puppet-3.8.5/0000775005276200011600000000000012650174565012751 5ustar jenkinsjenkinspuppet-3.8.5/COMMITTERS.md0000664005276200011600000002704712650174557014774 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](http://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](http://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-3.8.5/CONTRIBUTING.md0000664005276200011600000001105412650174557015204 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. ## Getting Started * Make sure you have a [Jira account](http://tickets.puppetlabs.com) * Make sure you have a [GitHub account](https://github.com/signup/free) * Submit a ticket for your issue, assuming one does not already exist. * Clearly describe the issue including steps to reproduce when it is a bug. * Make sure you fill in the earliest version that you know has the issue. * Fork the repository on GitHub ## Making Changes * Create a topic branch from where you want to base your work. * This is usually the master branch. * Only target release branches if you are certain your fix must be on that branch. * To quickly create a topic branch based on master; `git checkout -b fix/master/my_contribution master`. Please avoid working directly on the `master` branch. * Make commits of logical units. * Check for unnecessary whitespace with `git diff --check` before committing. * Make sure your commit messages are in the proper format. ```` (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. * Run _all_ the tests to assure nothing else was accidentally broken. ## Making Trivial Changes ### Documentation For changes of a trivial nature to comments and documentation, 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 '(doc)' instead of a ticket number. ```` (doc) Add documentation 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 '(doc)' in place of what would have been the ticket number in a non-documentation related commit. The body describes the nature of the new documentation or comments added. ```` ## Submitting Changes * Sign the [Contributor License Agreement](http://links.puppetlabs.com/cla). * Push your changes to a topic branch in your fork of the repository. * Submit a pull request to the repository in the puppetlabs organization. * Update your 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. # Additional Resources * [Puppet Labs community guildelines](http://docs.puppetlabs.com/community/community_guidelines.html) * [Bug tracker (Jira)](http://tickets.puppetlabs.com) * [Contributor License Agreement](http://links.puppetlabs.com/cla) * [General GitHub documentation](http://help.github.com/) * [GitHub pull request documentation](http://help.github.com/send-pull-requests/) * #puppet-dev IRC channel on freenode.org ([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-3.8.5/Gemfile0000664005276200011600000000705212650174557014251 0ustar jenkinsjenkinssource ENV['GEM_SOURCE'] || "https://rubygems.org" def location_for(place, fake_version = nil) if place =~ /^(git[:@][^#]*)#(.*)/ [fake_version, { :git => $1, :branch => $2, :require => false }].compact elsif place =~ /^file:\/\/(.*)/ ['>= 0', { :path => File.expand_path($1), :require => false }] else [place, { :require => false }] end end # C Ruby (MRI) or Rubinius, but NOT Windows platforms :ruby do gem 'pry', :group => :development gem 'yard', :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 if !ENV['PUPPET_LOADED'] gem "puppet", :path => File.dirname(__FILE__), :require => false end gem "facter", *location_for(ENV['FACTER_LOCATION'] || ['> 1.6', '< 3']) gem "hiera", *location_for(ENV['HIERA_LOCATION'] || '~> 1.0') gem "rake", "10.1.1", :require => false group(:development, :test) do gem "rspec", "~> 2.14.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", "~> 1.0" # json-schema does not support windows, so omit it from the platforms list # json-schema uses multi_json, but chokes with multi_json 1.7.9, so prefer 1.7.7 gem "multi_json", "1.7.7", :require => false, :platforms => [:ruby, :jruby] gem "json-schema", "2.1.1", :require => false, :platforms => [:ruby, :jruby] end group(:development) do if RUBY_PLATFORM != 'java' case RUBY_VERSION when /^1.8/ gem 'ruby-prof', "~> 0.13.1", :require => false else gem 'ruby-prof', :require => false end end end group(:extra) do gem "rack", "~> 1.4", :require => false gem "activerecord", '~> 3.2', :require => false # this pin for i18n should *not* be in the puppet-4+ branches # it's solely here because activerecord pulls in i18n and i18n # was recently updated to require 1.9.3 which breaks 1.8.7 specs gem "i18n", '~> 0.6.11', :require => false gem "couchrest", '~> 1.0', :require => false gem "net-ssh", '~> 2.1', :require => false gem "puppetlabs_spec_helper", :require => false # rest-client is used only by couchrest, so when # that dependency goes away, this one can also gem "rest-client", '1.6.7', :require => false gem "stomp", :require => false gem "tzinfo", :require => false case RUBY_PLATFORM when 'java' gem "jdbc-sqlite3", :require => false gem "msgpack-jruby", :require => false else gem "sqlite3", :require => false gem "msgpack", :require => false end end require 'yaml' data = YAML.load_file(File.join(File.dirname(__FILE__), 'ext', 'project_data.yaml')) bundle_platforms = data['bundle_platforms'] x64_platform = Gem::Platform.local.cpu == 'x64' data['gem_platform_dependencies'].each_pair do |gem_platform, info| next if gem_platform == 'x86-mingw32' && x64_platform next if gem_platform == 'x64-mingw32' && !x64_platform if bundle_deps = info['gem_runtime_dependencies'] bundle_platform = bundle_platforms[gem_platform] or raise "Missing bundle_platform" platform(bundle_platform.intern) do bundle_deps.each_pair do |name, version| gem(name, version, :require => false) end end end end if File.exists? "#{__FILE__}.local" eval(File.read("#{__FILE__}.local"), binding) end # vim:filetype=ruby puppet-3.8.5/LICENSE0000664005276200011600000000127612650174557013765 0ustar jenkinsjenkins Puppet - Automating Configuration Management. Copyright (C) 2005-2014 Puppet Labs Inc Puppet Labs can be contacted at: info@puppetlabs.com Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. puppet-3.8.5/README.md0000664005276200011600000000570612650174557014241 0ustar jenkinsjenkinsPuppet ====== [![Build Status](https://travis-ci.org/puppetlabs/puppet.png?branch=master)](https://travis-ci.org/puppetlabs/puppet) [![Inline docs](http://inch-ci.org/github/puppetlabs/puppet.png)](http://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](http://docs.puppetlabs.com). HTTP API -------- [HTTP API Index](api/docs/http_api_index.md) Installation ------------ The best way to run Puppet is with [Puppet Enterprise](http://puppetlabs.com/puppet/puppet-enterprise), which also includes orchestration features, a web console, and professional support. [The PE documentation is available here.](http://docs.puppetlabs.com/pe/latest) To install an open source release of Puppet, [see the installation guide on the docs site.](http://docs.puppetlabs.com/guides/installation.html) If you need to run Puppet from source as a tester or developer, [see the running from source guide on the docs site.](http://docs.puppetlabs.com/guides/from_source.html) 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](docs/quickstart.md). Once you are up and running, take a look at the [Contribution Documents](CONTRIBUTING.md) to see how to get your changes merged in. For more complete docs on developing with puppet you can take a look at the rest of the [developer documents](docs/index.md). License ------- See [LICENSE](LICENSE) file. Support ------- Please log tickets and issues at our [JIRA tracker](http://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](http://puppetlabs.com/misc/puppet-enterprise-lifecycle) puppet-3.8.5/Rakefile0000664005276200011600000000352412650174557014423 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' Dir['tasks/**/*.rake'].each { |t| load t } begin load File.join(RAKE_ROOT, 'ext', 'packaging', 'packaging.rake') rescue LoadError end build_defs_file = 'ext/build_defaults.yaml' if File.exist?(build_defs_file) begin require 'yaml' @build_defaults ||= YAML.load_file(build_defs_file) rescue Exception => e STDERR.puts "Unable to load yaml from #{build_defs_file}:" STDERR.puts e end @packaging_url = @build_defaults['packaging_url'] @packaging_repo = @build_defaults['packaging_repo'] raise "Could not find packaging url in #{build_defs_file}" if @packaging_url.nil? raise "Could not find packaging repo in #{build_defs_file}" if @packaging_repo.nil? namespace :package do desc "Bootstrap packaging automation, e.g. clone into packaging repo" task :bootstrap do if File.exist?("ext/#{@packaging_repo}") puts "It looks like you already have ext/#{@packaging_repo}. If you don't like it, blow it away with package:implode." else cd 'ext' do %x{git clone #{@packaging_url}} end end end desc "Remove all cloned packaging automation" task :implode do rm_rf "ext/#{@packaging_repo}" end end end task :default do sh %{rake -T} end task :spec do sh %{rspec #{ENV['TEST'] || ENV['TESTS'] || 'spec'}} end puppet-3.8.5/install.rb0000775005276200011600000003313112650174557014751 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' begin require 'ftools' # apparently on some system ftools doesn't get loaded $haveftools = true rescue LoadError puts "ftools not found. Using FileUtils instead.." $haveftools = false end 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}/, '')) if $haveftools File.install(cf, ocf, 0644, true) else FileUtils.install(cf, ocf, {:mode => 0644, :preserve => true, :verbose => true}) end end if $operatingsystem == 'windows' src_dll = 'ext/windows/eventlog/puppetres.dll' dst_dll = File.join(InstallOptions.bin_dir, 'puppetres.dll') if $haveftools File.install(src_dll, dst_dll, 0644, true) else FileUtils.install(src_dll, dst_dll, {:mode => 0644, :preserve => true, :verbose => true}) end 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) if $haveftools File.makedirs(op, true) File.chmod(0755, op) File.install(lf, olf, 0644, true) else FileUtils.makedirs(op, {:mode => 0755, :verbose => true}) FileUtils.chmod(0755, op) FileUtils.install(lf, olf, {:mode => 0644, :preserve => true, :verbose => true}) end 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) if $haveftools File.makedirs(om, true) File.chmod(0755, om) File.install(mf, omf, 0644, true) else FileUtils.makedirs(om, {:mode => 0755, :verbose => true}) FileUtils.chmod(0755, om) FileUtils.install(mf, omf, {:mode => 0644, :preserve => true, :verbose => true}) end gzip = %x{which gzip} gzip.chomp! %x{#{gzip} -f #{omf}} 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 $operatingsystem = Facter["operatingsystem"].value InstallOptions.configs = true # Only try to do docs if we're sure they have rdoc if $haverdoc InstallOptions.rdoc = true InstallOptions.ri = $operatingsystem != "windows" 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/puppet') do |configdir| InstallOptions.configdir = configdir end opts.on('--bindir[=OPTIONAL]', 'Installation directory for binaries', 'overrides RbConfig::CONFIG["bindir"]') do |bindir| InstallOptions.bindir = bindir 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('--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 if not InstallOptions.configdir.nil? configdir = InstallOptions.configdir elsif $operatingsystem == "windows" begin 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 configdir = File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "etc") else configdir = "/etc/puppet" end if not InstallOptions.bindir.nil? bindir = InstallOptions.bindir else bindir = RbConfig::CONFIG['bindir'] 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) bindir = join(destdir, bindir) mandir = join(destdir, mandir) sitelibdir = join(destdir, sitelibdir) FileUtils.makedirs(configdir) if InstallOptions.configs FileUtils.makedirs(bindir) FileUtils.makedirs(mandir) FileUtils.makedirs(sitelibdir) InstallOptions.site_dir = sitelibdir InstallOptions.config_dir = configdir InstallOptions.bin_dir = bindir InstallOptions.lib_dir = libdir InstallOptions.man_dir = mandir 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 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}" contents = ip.readlines contents.shift if contents[0] =~ /^#!/ op.write contents.join end end if $operatingsystem == "windows" installed_wrapper = false 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 set RUBY_BIN=%~dp0 set RUBY_BIN=%RUBY_BIN:\\=/% "%RUBY_BIN%ruby.exe" -x "%RUBY_BIN%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 installed_wrapper = true 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}) 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/**/*}) check_prereqs prepare_installation #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_libs(libs) do_man(man) unless $operatingsystem == "windows" end puppet-3.8.5/bin/0000775005276200011600000000000012650174565013521 5ustar jenkinsjenkinspuppet-3.8.5/bin/extlookup2hiera0000775005276200011600000000223512650174557016577 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'optparse' require 'csv' options = {:in => nil, :out => nil, :format => :yaml} OptionParser.new do |opts| opts.banner = "Converter for extlookup CSV files into Hiera JSON and YAML files" opts.on("--in FILE", "-i", "Input CSV file") do |v| options[:in] = v end opts.on("--out FILE", "-o", "Output Hiera file") do |v| options[:out] = v end opts.on("--json", "-j", "Create JSON format file") do |v| options[:format] = :json end end.parse! if options[:in].nil? || options[:out].nil? STDERR.puts "Please specify an input and output file with --in and --out" exit 1 end unless File.exist?(options[:in]) STDERR.puts "Cannot find input file #{options[:in]}" exit 1 end csvdata = CSV.read(options[:in]) hieradata = {} csvdata.each do |d| d = d.map{|item| item.to_s} if d.size > 2 hieradata[d[0]] = d[1, d.size].flatten else hieradata[d[0]] = d[1] end end case options[:format] when :yaml require 'yaml' File.open(options[:out], "w") {|f| f.write hieradata.to_yaml} when :json require 'rubygems' require 'json' File.open(options[:out], "w") {|f| f.write JSON.pretty_generate hieradata} end puppet-3.8.5/bin/puppet0000775005276200011600000000040112650174557014760 0ustar jenkinsjenkins#!/usr/bin/env ruby # For security reasons, ensure that '.' is not on the load path # This is primarily for 1.8.7 since 1.9.2+ doesn't put '.' on the load path $LOAD_PATH.delete '.' require 'puppet/util/command_line' Puppet::Util::CommandLine.new.execute puppet-3.8.5/lib/0000775005276200011600000000000012650174565013517 5ustar jenkinsjenkinspuppet-3.8.5/lib/hiera/0000775005276200011600000000000012650174565014607 5ustar jenkinsjenkinspuppet-3.8.5/lib/hiera/backend/0000775005276200011600000000000012650174565016176 5ustar jenkinsjenkinspuppet-3.8.5/lib/hiera/backend/puppet_backend.rb0000664005276200011600000000550712650174557021517 0ustar jenkinsjenkinsrequire 'hiera/backend' class Hiera module Backend class Puppet_backend def initialize Hiera.debug("Hiera Puppet backend starting") end def hierarchy(scope, override) begin data_class = Config[:puppet][:datasource] || "data" rescue data_class = "data" end calling_class = scope.resource.name.to_s.downcase calling_module = calling_class.split("::").first hierarchy = Config[:hierarchy] || [calling_class, calling_module] hierarchy = [hierarchy].flatten.map do |klass| klass = Backend.parse_string(klass, scope, { "calling_class" => calling_class, "calling_module" => calling_module } ) next if klass == "" [data_class, klass].join("::") end.compact hierarchy << [calling_class, data_class].join("::") unless calling_module == calling_class hierarchy << [calling_module, data_class].join("::") end hierarchy.insert(0, [data_class, override].join("::")) if override hierarchy end def lookup(key, scope, order_override, resolution_type) answer = nil Hiera.debug("Looking up #{key} in Puppet backend") Puppet::Parser::Functions.function(:include) loaded_classes = scope.catalog.classes hierarchy(scope, order_override).each do |klass| Hiera.debug("Looking for data in #{klass}") varname = [klass, key].join("::") temp_answer = nil unless loaded_classes.include?(klass) begin if scope.respond_to?(:function_include) scope.function_include([klass]) else scope.real.function_include([klass]) end temp_answer = scope[varname] Hiera.debug("Found data in class #{klass}") rescue end else temp_answer = scope[varname] end # Note that temp_answer might be define but false. if temp_answer.nil? next else # For array resolution we just append to the array whatever we # find, we then go onto the next file and keep adding to the array. # # For priority searches we break after the first found data item. case resolution_type when :array answer ||= [] answer << Backend.parse_answer(temp_answer, scope) when :hash answer ||= {} answer = Backend.parse_answer(temp_answer, scope).merge answer else answer = Backend.parse_answer(temp_answer, scope) break end end end answer end end end end puppet-3.8.5/lib/hiera/puppet_function.rb0000664005276200011600000000424612650174557020365 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, nil)) end def hiera_with_default(scope, key, default, override = nil) undefined = (@@undefined_value ||= Object.new) result = lookup(scope, key, undefined, override) post_lookup(scope, key, result.equal?(undefined) ? default : result) end def hiera_block1(scope, key, &default_block) common(scope, key, nil, default_block) end def hiera_block2(scope, key, override, &default_block) common(scope, key, override, default_block) end def common(scope, key, override, default_block) undefined = (@@undefined_value ||= Object.new) result = lookup(scope, key, undefined, override) post_lookup(scope, key, result.equal?(undefined) ? default_block.call(key) : result) end private :common def lookup(scope, key, default, override) HieraPuppet.lookup(key, default, scope, override, merge_type) end def merge_type :priority end def post_lookup(scope, key, result) result end end puppet-3.8.5/lib/hiera/scope.rb0000664005276200011600000000210112650174557016240 0ustar jenkinsjenkinsclass Hiera class Scope CALLING_CLASS = "calling_class" CALLING_MODULE = "calling_module" MODULE_NAME = "module_name" attr_reader :real def initialize(real) @real = real end def [](key) if key == CALLING_CLASS ans = find_hostclass(@real) elsif key == CALLING_MODULE ans = @real.lookupvar(MODULE_NAME) else ans = @real.lookupvar(key) end if ans.nil? or ans == "" nil else ans end end def include?(key) if key == CALLING_CLASS or key == CALLING_MODULE true else @real.lookupvar(key) != "" end 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-3.8.5/lib/hiera_puppet.rb0000664005276200011600000000377512650174557016546 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") 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 config_file = nil if Puppet.settings[:hiera_config].is_a?(String) expanded_config_file = File.expand_path(Puppet.settings[:hiera_config]) if Puppet::FileSystem.exist?(expanded_config_file) config_file = expanded_config_file end elsif Puppet.settings[:confdir].is_a?(String) expanded_config_file = File.expand_path(File.join(Puppet.settings[:confdir], '/hiera.yaml')) if Puppet::FileSystem.exist?(expanded_config_file) config_file = expanded_config_file end end config_file end end puppet-3.8.5/lib/puppet.rb0000664005276200011600000002340412650174557015365 0ustar jenkinsjenkinsrequire 'puppet/version' # 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' require 'puppet/external/pson/common' require 'puppet/external/pson/version' require 'puppet/external/pson/pure' #------------------------------------------------------------ # 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/context' require 'puppet/environments' class << self include Puppet::Util attr_reader :features attr_writer :name 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 # The services running in this process. @services ||= [] require 'puppet/util/logging' extend Puppet::Util::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 # Ensure that all environment caches are cleared if we're changing the parser lookup(:environments).clear_all if param == :parser 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' def self.genmanifest if Puppet[:genmanifest] puts Puppet.settings.to_manifest exit(0) end end # Parse the config file for this process. # @deprecated Use {initialize_settings} def self.parse_config() Puppet.deprecation_warning("Puppet.parse_config is deprecated; please use Faces API (which will handle settings and state management for you), or (less desirable) call Puppet.initialize_settings") Puppet.initialize_settings 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 # Initialize puppet's settings for a specified run_mode. # # @deprecated Use {initialize_settings} def self.initialize_settings_for_run_mode(run_mode) Puppet.deprecation_warning("initialize_settings_for_run_mode may be removed in a future release, as may run_mode itself") do_initialize_settings_for_run_mode(run_mode, []) 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 Puppet::Util::Log.level = Puppet[:log_level] end private_class_method :do_initialize_settings_for_run_mode # 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::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 # Set default for YAML.load to unsafe so we don't affect programs # requiring puppet -- in puppet we will call safe explicitly SafeYAML::OPTIONS[:default_mode] = :unsafe # The bindings used for initialization of puppet # @api private def self.base_context(settings) environments = settings[:environmentpath] modulepath = Puppet::Node::Environment.split_path(settings[:basemodulepath]) if environments.empty? loaders = [Puppet::Environments::Legacy.new] else loaders = Puppet::Environments::Directories.from_path(environments, modulepath) # in case the configured environment (used for the default sometimes) # doesn't exist default_environment = Puppet[:environment].to_sym if default_environment == :production loaders << Puppet::Environments::StaticPrivate.new( Puppet::Node::Environment.create(Puppet[:environment].to_sym, [], 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 } } 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) ensure lookup(:root_environment).instance_variable_set(:@future_parser, nil) 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) # Is the future parser in effect for the given environment, or in :current_environment if no # environment is given. # def self.future_parser?(in_environment = nil) env = in_environment || Puppet.lookup(:current_environment) { return Puppet[:parser] == 'future' } env.future_parser? end 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/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' puppet-3.8.5/lib/puppet/0000775005276200011600000000000012650174565015034 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/agent.rb0000664005276200011600000000577112650174557016472 0ustar jenkinsjenkinsrequire 'puppet/application' # 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 attr_reader :client_class, :client, :splayed, :should_fork def initialize(client_class, should_fork=true) @splayed = false @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 running? Puppet.notice "Run of #{client_class} already in progress; skipping (#{lockfile_path} exists)" return end if disabled? Puppet.notice "Skipping run of #{client_class}; administratively disabled (Reason: '#{disable_message}');\nUse 'puppet agent --enable' to re-enable." 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 do |client| begin client_args = client_options.merge(:pluginsync => Puppet[:pluginsync]) lock { client.run(client_args) } rescue SystemExit,NoMemoryError raise rescue Exception => detail Puppet.log_exception(detail, "Could not run #{client_class}: #{detail}") end end end true end Puppet.notice "Shutdown/restart in progress (#{Puppet::Application.run_status.inspect}); skipping run" unless block_run result end def stopping? Puppet::Application.stop_requested? end # 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)" sleep(time) @splayed = true end def run_in_fork(forking = true) return yield unless forking or Puppet.features.windows? child_pid = Kernel.fork do $0 = "puppet agent: applying configuration" begin exit(yield) rescue SystemExit exit(-1) rescue NoMemoryError exit(-2) end 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 begin @client = client_class.new rescue SystemExit,NoMemoryError raise rescue Exception => detail Puppet.log_exception(detail, "Could not create instance of #{client_class}: #{detail}") return end yield @client ensure @client = nil end end puppet-3.8.5/lib/puppet/agent/0000775005276200011600000000000012650174565016132 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/agent/disabler.rb0000664005276200011600000000265112650174557020251 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.puppetlabs.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-3.8.5/lib/puppet/agent/locker.rb0000664005276200011600000000212212650174557017734 0ustar jenkinsjenkinsrequire 'puppet/util/pidlock' # 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.puppetlabs.com/agent_lockfiles module Puppet::Agent::Locker # Yield if we get a lock, else do nothing. Return # true/false depending on whether we get the lock. def lock if lockfile.lock begin yield ensure lockfile.unlock end end end def running? 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-3.8.5/lib/puppet/application.rb0000664005276200011600000004247212650174557017676 0ustar jenkinsjenkinsrequire 'optparse' require 'puppet/util/command_line' require 'puppet/util/plugins' require 'puppet/util/constant_inflector' require 'puppet/error' 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 functionnality 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 overriden. # # === 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 SHOULD_PARSE_CONFIG_DEPRECATION_MSG = "is no longer supported; config file parsing " + "is now controlled by the puppet engine, rather than by individual applications. This " + "method will be removed in a future version of puppet." def should_parse_config Puppet.deprecation_warning("should_parse_config " + SHOULD_PARSE_CONFIG_DEPRECATION_MSG) end def should_not_parse_config Puppet.deprecation_warning("should_not_parse_config " + SHOULD_PARSE_CONFIG_DEPRECATION_MSG) end def should_parse_config? Puppet.deprecation_warning("should_parse_config? " + SHOULD_PARSE_CONFIG_DEPRECATION_MSG) true 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}'. #{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'") 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 # 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 end # Every app responds to --help option("--help", "-h") do |v| puts help exit 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 # 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. Also, Daniel suggests that we can probably get rid of this "plugin_hook" # pattern, but we need to check with PE and the community first. --cprice 2012-03-16 # exit_on_fail("get application-specific default settings") do plugin_hook('initialize_app_defaults') { initialize_app_defaults } end Puppet.push_context(Puppet.base_context(Puppet.settings), "Update for application settings (#{self.class.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 self.class.run_mode.name == :agent configured_environment = 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") require 'puppet/util/instrumentation' Puppet::Util::Instrumentation.init exit_on_fail("initialize") { plugin_hook('preinit') { preinit } } exit_on_fail("parse application options") { plugin_hook('parse_options') { parse_options } } exit_on_fail("prepare for execution") { plugin_hook('setup') { setup } } exit_on_fail("configure routes from #{Puppet[:route_file]}") { configure_indirector_routes } exit_on_fail("log runtime debug info") { log_runtime_environment } exit_on_fail("run") { plugin_hook('run_command') { 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 if options[:debug] || options[:verbose] Puppet::Util::Log.newdestination(:console) end set_log_level Puppet::Util::Log.setup_default unless options[:setdest] end def set_log_level if options[:debug] Puppet::Util::Log.level = :debug elsif options[:verbose] Puppet::Util::Log.level = :info end end def handle_logdest_arg(arg) begin Puppet::Util::Log.newdestination(arg) options[:setdest] = true rescue => detail Puppet.log_exception(detail) end end def configure_indirector_routes route_file = Puppet[:route_file] if Puppet::FileSystem.exist?(route_file) routes = YAML.load_file(route_file) application_routes = routes[name.to_s] Puppet::Indirector.configure_routes(application_routes) if application_routes end 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 if RUBY_VERSION >= '1.9.3' 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 #{name}" end def plugin_hook(step,&block) Puppet::Plugins.send("before_application_#{step}",:application_object => self) x = yield Puppet::Plugins.send("after_application_#{step}",:application_object => self, :return_value => x) x end private :plugin_hook end end puppet-3.8.5/lib/puppet/application/0000775005276200011600000000000012650174565017337 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/application/agent.rb0000664005276200011600000003574712650174557021003 0ustar jenkinsjenkinsrequire 'puppet/application' require 'puppet/run' require 'puppet/daemon' require 'puppet/util/pidlock' 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, :client => true, :fqdn => nil, :serve => [], :digest => 'SHA256', :graph => true, :fingerprint => false, }.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("--no-client") do |arg| options[:client] = false end 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 def help <<-'HELP' puppet-agent(8) -- The puppet agent daemon ======== 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 ] [--no-client] [--noop] [-o|--onetime] [-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' or 'fingerprint' are useful. 'test' enables verbose logging, causes the daemon to stay in the foreground, exits if the server's configuration is invalid (this happens if, for instance, you've left a syntax error on the server), and exits after running the configuration once (rather than hanging around as a long-running process). '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 http://docs.puppetlabs.com/references/stable/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 transaction information via exit codes. If this is enabled, an exit code of '2' means there were changes, an exit code of '4' means there were failures during the transaction, and an exit code of '6' means there were 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 * --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.) * --no-client: Do not create a config client. This will cause the daemon to start but not check configuration unless it is triggered with `puppet kick`. This only makes sense when puppet agent is being run with listen = true in puppet.conf or was started with the `--listen` option. * --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.) * --test: Enable the most common options used for testing. These are 'onetime', 'verbose', 'ignorecache', '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 Labs, 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]) 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 '#{options[:digest]}'" end puts digest.to_s end def onetime(daemon) if Puppet[:listen] Puppet.notice "Ignoring --listen on onetime run" end unless options[:client] Puppet.err "onetime is specified but there is no client" exit(43) return end daemon.set_signal_traps begin exitstatus = daemon.agent.run 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) if Puppet[:listen] setup_listen(daemon) end Puppet.notice "Starting Puppet client version #{Puppet.version}" daemon.start end # Enable all of the most common test options. def setup_test Puppet.settings.handlearg("--ignorecache") 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 setup_test if options[:test] setup_logs exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? if options[:fqdn] Puppet[:certname] = options[:fqdn] end Puppet.settings.use :main, :agent, :ssl # Always ignoreimport for agent. It really shouldn't even try to import, # but this is just a temporary band-aid. Puppet[:ignoreimport] = true 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_listen(daemon) Puppet.warning "Puppet --listen / kick is deprecated. See http://links.puppetlabs.com/puppet-kick-deprecation" unless Puppet::FileSystem.exist?(Puppet[:rest_authconfig]) Puppet.err "Will not start without authorization file #{Puppet[:rest_authconfig]}" exit(14) end require 'puppet/network/server' # No REST handlers yet. server = Puppet::Network::Server.new(Puppet[:bindaddress], Puppet[:puppetport]) daemon.server = server end def setup_agent # We need to make the client either way, we just don't start it # if --no-client is set. require 'puppet/agent' require 'puppet/configurer' agent = Puppet::Agent.new(Puppet::Configurer, (not(Puppet[:onetime]))) enable_disable_client(agent) if options[:enable] or options[:disable] @agent = agent if options[:client] 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-3.8.5/lib/puppet/application/apply.rb0000664005276200011600000002055112650174557021015 0ustar jenkinsjenkinsrequire 'puppet/application' require 'puppet/configurer' require 'puppet/util/profiler/aggregate' class Puppet::Application::Apply < Puppet::Application 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 help <<-'HELP' puppet-apply(8) -- Apply Puppet manifests locally ======== 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 http://docs.puppetlabs.com/references/stable/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 transaction information via exit codes. If this is enabled, an exit code of '2' means there were changes, an exit code of '4' means there were failures during the transaction, and an exit code of '6' means there were both changes and failures. * --help: Print this help message * --loadclasses: Load any stored classes. 'puppet agent' caches configured classes (usually at /etc/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 Labs, 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 = ::File.read(options[:catalog]) end catalog = read_catalog(text) apply_catalog(catalog) 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}" unless Puppet::FileSystem.exist?(manifest) Puppet.warning("Only one file can be applied per run. Skipping #{command_line.args.join(', ')}") if command_line.args.size > 0 end 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 #{Puppet[:node_name_value]}" end Puppet[:node_name_value] = facts.values[Puppet[:node_name_fact]] facts.name = Puppet[:node_name_value] end configured_environment = Puppet.lookup(:current_environment) apply_environment = manifest ? configured_environment.override_with(:manifest => manifest) : configured_environment Puppet.override({:current_environment => apply_environment}, "For puppet apply") do # Find our Node unless node = Puppet::Node.indirection.find(Puppet[:node_name_value]) raise "Could not find node #{Puppet[:node_name_value]}" end # Merge in the facts. node.merge(facts.values) if 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" exit(63) end node.classes = ::File.read(file).split(/[\s\n]+/) end end begin # Compile our catalog starttime = Time.now catalog = Puppet::Resource::Catalog.indirection.find(node.name, :use_node => node) # 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 = 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("--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? Puppet::Util::Log.newdestination(:console) unless options[:setdest] Signal.trap(:INT) do $stderr.puts "Exiting" exit(1) 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(:debug), "apply")) end end private def read_catalog(text) begin catalog = Puppet::Resource::Catalog.convert_from(Puppet::Resource::Catalog.default_format,text) catalog = Puppet::Resource::Catalog.pson_create(catalog) unless catalog.is_a?(Puppet::Resource::Catalog) rescue => detail raise Puppet::Error, "Could not deserialize catalog from pson: #{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-3.8.5/lib/puppet/application/ca.rb0000664005276200011600000000017512650174557020253 0ustar jenkinsjenkinsrequire 'puppet/application/face_base' class Puppet::Application::Ca < Puppet::Application::FaceBase run_mode :master end puppet-3.8.5/lib/puppet/application/catalog.rb0000664005276200011600000000017512650174557021302 0ustar jenkinsjenkinsrequire 'puppet/application/indirection_base' class Puppet::Application::Catalog < Puppet::Application::IndirectionBase end puppet-3.8.5/lib/puppet/application/cert.rb0000664005276200011600000001611112650174557020622 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| Puppet::Util::Log.level = :debug 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("--verbose", "-v") do |arg| Puppet::Util::Log.level = :info end def help <<-'HELP' puppet-cert(8) -- Manage certificates and requests ======== 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. * 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). * 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. * 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 http://docs.puppetlabs.com/references/stable/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', 'clean', 'list', and 'fingerprint' actions. * --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 Labs, 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 apply(@ca, :revoke, options.merge(:to => hosts)) if subcommand == :destroy apply(@ca, subcommand, options.merge(:to => hosts, :digest => @digest)) rescue => detail Puppet.log_exception(detail) exit(24) end end def setup require 'puppet/ssl/certificate_authority' exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? 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-3.8.5/lib/puppet/application/certificate.rb0000664005276200011600000000110112650174557022140 0ustar jenkinsjenkinsrequire 'puppet/application/indirection_base' class Puppet::Application::Certificate < Puppet::Application::IndirectionBase def setup 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-3.8.5/lib/puppet/application/certificate_request.rb0000664005276200011600000000057212650174557023723 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-3.8.5/lib/puppet/application/certificate_revocation_list.rb0000664005276200011600000000060212650174557025431 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-3.8.5/lib/puppet/application/config.rb0000664005276200011600000000015612650174557021134 0ustar jenkinsjenkinsrequire 'puppet/application/face_base' class Puppet::Application::Config < Puppet::Application::FaceBase end puppet-3.8.5/lib/puppet/application/describe.rb0000664005276200011600000001231412650174557021446 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("^(.{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/ indent = nil # 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 help <<-'HELP' puppet-describe(8) -- Display help about resource types ======== 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 Labs, 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-3.8.5/lib/puppet/application/device.rb0000664005276200011600000001671412650174557021135 0ustar jenkinsjenkinsrequire 'puppet/application' 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 { :waitforcert => nil, :detailed_exitcodes => false, :verbose => false, :debug => false, :centrallogs => false, :setdest => false, }.each do |opt,val| options[opt] = val end @args = {} end option("--centrallogging") option("--debug","-d") option("--verbose","-v") 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("--port PORT","-p") do |arg| @args[:Port] = arg end def help <<-'HELP' puppet-device(8) -- Manage remote network devices ======== SYNOPSIS -------- Retrieves all configurations from the puppet master and apply them to the remote devices configured in /etc/puppet/device.conf. Currently must be run out periodically, using cron or something similar. USAGE ----- puppet device [-d|--debug] [--detailed-exitcodes] [-V|--version] [-h|--help] [-l|--logdest syslog||console] [-v|--verbose] [-w|--waitforcert ] DESCRIPTION ----------- Once the client has a signed certificate for a given remote device, it will retrieve its configuration and apply it. USAGE NOTES ----------- One need a /etc/puppet/device.conf file with the following content: [remote.device.fqdn] type url where: * type: the current device type (the only value at this time is cisco) * url: an url allowing to connect to the device Supported url must conforms to: scheme://user:password@hostname/?query with: * scheme: either ssh or telnet * user: username, can be omitted depending on the switch/router configuration * password: the connection password * query: this is device specific. Cisco devices supports an enable parameter whose value would be the enable password. 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. * --debug: Enable full debugging. * --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 preceeding exit codes happened. * --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. * --verbose: Turn on verbose reporting. * --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. EXAMPLE ------- $ puppet device --server puppet.domain.com AUTHOR ------ Brice Figureau COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end def main vardir = Puppet[:vardir] confdir = Puppet[:confdir] certname = Puppet[:certname] # find device list require 'puppet/util/network_device/config' devices = Puppet::Util::NetworkDevice::Config.devices if devices.empty? Puppet.err "No device found in #{Puppet[:deviceconfig]}" exit(1) end returns = 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 Puppet.info "starting applying configuration to #{device.name} at #{scheme}#{device_url.host}#{port}#{device_url.path}" # override local $vardir and $certname Puppet[:confdir] = ::File.join(Puppet[:devicedir], device.name) Puppet[:vardir] = ::File.join(Puppet[:devicedir], device.name) Puppet[:certname] = device.name # this will reload and recompute default settings and create the devices sub vardir, or we hope so :-) Puppet.settings.use :main, :agent, :ssl # this init the device singleton, so that the facts terminus # and the various network_device provider can use it Puppet::Util::NetworkDevice.init(device) # 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[:pluginsync]) rescue => detail Puppet.log_exception(detail) # If we rescued an error, then we return 1 as the exit code 1 ensure Puppet[:vardir] = vardir Puppet[:confdir] = confdir Puppet[:certname] = certname Puppet::SSL::Host.reset 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 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 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 # Always ignoreimport for agent. It really shouldn't even try to import, # but this is just a temporary band-aid. Puppet[:ignoreimport] = true # 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 puppet-3.8.5/lib/puppet/application/doc.rb0000664005276200011600000001620312650174557020434 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}" 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}" 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 help <<-'HELP' puppet-doc(8) -- Generate Puppet documentation and references ======== SYNOPSIS -------- Generates a reference for all Puppet types. Largely meant for internal Puppet Labs use. USAGE ----- puppet doc [-a|--all] [-h|--help] [-l|--list] [-o|--outputdir ] [-m|--mode text|pdf|rdoc] [-r|--reference ] [--charset ] [] DESCRIPTION ----------- If mode is not 'rdoc', then this command generates a Markdown document 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 Labs web site. In 'rdoc' mode, this command generates an html RDoc hierarchy describing the manifests that are in 'manifestdir' and 'modulepath' configuration directives. The generated documentation directory is doc by default but can be changed with the 'outputdir' option. If the command is run with the name of a manifest file as an argument, puppet doc will output a single manifest's documentation on stdout. OPTIONS ------- * --all: Output the docs for all of the reference types. In 'rdoc' mode, this also outputs documentation for all resources. * --help: Print this help message * --outputdir: Used only in 'rdoc' mode. The directory to which the rdoc output should be written. * --mode: Determine the output mode. Valid modes are 'text', 'pdf' and 'rdoc'. The 'pdf' mode creates PDF formatted files in the /tmp directory. The default mode is 'text'. * --reference: Build a particular reference. Get a list of references by running 'puppet doc --list'. * --charset: Used only in 'rdoc' mode. It sets the charset used in the html files produced. * --manifestdir: Used only in 'rdoc' mode. The directory to scan for stand-alone manifests. If not supplied, puppet doc will use the manifestdir from puppet.conf. * --modulepath: Used only in 'rdoc' mode. The directory or directories to scan for modules. If not supplied, puppet doc will use the modulepath from puppet.conf. * --environment: Used only in 'rdoc' mode. The configuration environment from which to read the modulepath and manifestdir settings, when reading said settings from puppet.conf. EXAMPLE ------- $ puppet doc -r type > /tmp/type_reference.markdown or $ puppet doc --outputdir /tmp/rdoc --mode rdoc /path/to/manifests or $ puppet doc /etc/puppet/manifests/site.pp or $ puppet doc -m pdf -r configuration AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, 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.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}") 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}" 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}") 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(dummy_argument=:work_arround_for_ruby_GC_bug) # 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" or option[:opt] == "--manifestdir" 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 # Handle the logging settings. if options[:debug] Puppet::Util::Log.level = :debug elsif options[:verbose] Puppet::Util::Log.level = :info else Puppet::Util::Log.level = :warning end Puppet::Util::Log.newdestination(:console) end end puppet-3.8.5/lib/puppet/application/face_base.rb0000664005276200011600000002132312650174557021556 0ustar jenkinsjenkinsrequire 'puppet/application' require 'puppet/face' require 'optparse' require 'pp' class Puppet::Application::FaceBase < Puppet::Application option("--debug", "-d") do |arg| Puppet::Util::Log.level = :debug end option("--verbose", "-v") do |_| Puppet::Util::Log.level = :info end option("--render-as FORMAT") do |format| self.render_as = format.to_sym end attr_accessor :face, :action, :type, :arguments, :render_as def render_as=(format) if format == :json then @render_as = Puppet::Network::FormatHandler.format(:pson) else @render_as = Puppet::Network::FormatHandler.format(format) end @render_as or raise ArgumentError, "I don't know how to render '#{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 face = @face.name action = action_name.nil? ? 'default' : "'#{action_name}'" msg = "'#{face}' has no #{action} action. See `puppet help #{face}`." 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 |option| option = @action.get_option(option) # make it the object. self.class.option(*option.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 #{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 s = arity == 2 ? '' : 's' raise ArgumentError, "puppet #{@face.name} #{@action.name} takes #{arity-1} argument#{s}, but you gave #{count-1}" end 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.name} #{@action.name}' for usage" ensure exit status end end puppet-3.8.5/lib/puppet/application/facts.rb0000664005276200011600000000017312650174557020766 0ustar jenkinsjenkinsrequire 'puppet/application/indirection_base' class Puppet::Application::Facts < Puppet::Application::IndirectionBase end puppet-3.8.5/lib/puppet/application/file.rb0000664005276200011600000000017212650174557020604 0ustar jenkinsjenkinsrequire 'puppet/application/indirection_base' class Puppet::Application::File < Puppet::Application::IndirectionBase end puppet-3.8.5/lib/puppet/application/filebucket.rb0000664005276200011600000001030612650174557022002 0ustar jenkinsjenkinsrequire 'puppet/application' class Puppet::Application::Filebucket < Puppet::Application option("--bucket BUCKET","-b") option("--debug","-d") option("--local","-l") option("--remote","-r") option("--verbose","-v") attr :args def help <<-'HELP' puppet-filebucket(8) -- Store and retrieve files in a filebucket ======== SYNOPSIS -------- A stand-alone Puppet filebucket client. USAGE ----- puppet filebucket [-h|--help] [-V|--version] [-d|--debug] [-v|--verbose] [-l|--local] [-r|--remote] [-s|--server ] [-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. 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'. 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 http://docs.puppetlabs.com/references/stable/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 * --local: Use the local filebucket. This will use the default configuration information. * --remote: Use a remote filebucket. This will use the default configuration information. * --server: The server to send the file to, instead of locally. * --verbose: Print extra information. * --version: Print version information. EXAMPLE ------- $ puppet filebucket backup /etc/passwd /etc/passwd: 429b225650b912a2ee067b0a4cf1e949 $ puppet filebucket restore /tmp/passwd 429b225650b912a2ee067b0a4cf1e949 AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, 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}.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" next end unless FileTest.readable?(file) $stderr.puts "#{file}: cannot read file" next end md5 = @client.backup(file) puts "#{file}: #{md5}" end end def restore file = args.shift md5 = args.shift @client.restore(file, md5) 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[:bucketdir] @client = Puppet::FileBucket::Dipper.new(:Path => path) else @client = Puppet::FileBucket::Dipper.new(:Server => Puppet[:server]) end rescue => detail Puppet.log_exception(detail) exit(1) end end end puppet-3.8.5/lib/puppet/application/help.rb0000664005276200011600000000015412650174557020615 0ustar jenkinsjenkinsrequire 'puppet/application/face_base' class Puppet::Application::Help < Puppet::Application::FaceBase end puppet-3.8.5/lib/puppet/application/indirection_base.rb0000664005276200011600000000016712650174557023172 0ustar jenkinsjenkinsrequire 'puppet/application/face_base' class Puppet::Application::IndirectionBase < Puppet::Application::FaceBase end puppet-3.8.5/lib/puppet/application/inspect.rb0000664005276200011600000001256312650174557021341 0ustar jenkinsjenkinsrequire 'puppet/application' class Puppet::Application::Inspect < Puppet::Application run_mode :agent option("--debug","-d") option("--verbose","-v") option("--logdest LOGDEST", "-l") do |arg| handle_logdest_arg(arg) end def help <<-'HELP' puppet-inspect(8) -- Send an inspection report ======== SYNOPSIS -------- Prepares and submits an inspection report to the puppet master. USAGE ----- puppet inspect [--archive_files] [--archive_file_server] DESCRIPTION ----------- This command uses the cached catalog from the previous run of 'puppet agent' to determine which attributes of which resources have been marked as auditable with the 'audit' metaparameter. It then examines the current state of the system, writes the state of the specified resource attributes to a report, and submits the report to the puppet master. Puppet inspect does not run as a daemon, and must be run manually or from cron. OPTIONS ------- Any configuration setting which is valid in the configuration file is also a valid long argument, e.g. '--server=master.domain.com'. See the configuration file documentation at http://docs.puppetlabs.com/references/latest/configuration.html for the full list of acceptable settings. * --archive_files: During an inspect run, whether to archive files whose contents are audited to a file bucket. * --archive_file_server: During an inspect run, the file bucket server to archive files to if archive_files is set. The default value is '$server'. AUTHOR ------ Puppet Labs COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end def setup exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? raise "Inspect requires reporting to be enabled. Set report=true in puppet.conf to enable reporting." unless Puppet[:report] @report = Puppet::Transaction::Report.new("inspect") Puppet::Util::Log.newdestination(@report) Puppet::Util::Log.newdestination(:console) unless options[:setdest] Signal.trap(:INT) do $stderr.puts "Exiting" exit(1) end set_log_level Puppet::Transaction::Report.indirection.terminus_class = :rest Puppet::Resource::Catalog.indirection.terminus_class = :yaml end def preinit require 'puppet' require 'puppet/file_bucket/dipper' end def run_command benchmark(:notice, "Finished inspection") do retrieval_starttime = Time.now unless catalog = Puppet::Resource::Catalog.indirection.find(Puppet[:certname]) raise "Could not find catalog for #{Puppet[:certname]}" end @report.configuration_version = catalog.version @report.environment = Puppet[:environment] inspect_starttime = Time.now @report.add_times("config_retrieval", inspect_starttime - retrieval_starttime) if Puppet[:archive_files] dipper = Puppet::FileBucket::Dipper.new(:Server => Puppet[:archive_file_server]) end catalog.to_ral.resources.each do |ral_resource| audited_attributes = ral_resource[:audit] next unless audited_attributes status = Puppet::Resource::Status.new(ral_resource) begin audited_resource = ral_resource.to_resource rescue StandardError => detail ral_resource.log_exception(detail, "Could not inspect #{ral_resource}; skipping: #{detail}") audited_attributes.each do |name| event = ral_resource.event( :property => name, :status => "failure", :audited => true, :message => "failed to inspect #{name}" ) status.add_event(event) end else audited_attributes.each do |name| next if audited_resource[name].nil? # Skip :absent properties of :absent resources. Really, it would be nicer if the RAL returned nil for those, but it doesn't. ~JW if name == :ensure or audited_resource[:ensure] != :absent or audited_resource[name] != :absent event = ral_resource.event( :previous_value => audited_resource[name], :property => name, :status => "audit", :audited => true, :message => "inspected value is #{audited_resource[name].inspect}" ) status.add_event(event) end end end if Puppet[:archive_files] and ral_resource.type == :file and audited_attributes.include?(:content) path = ral_resource[:path] if ::File.readable?(path) begin dipper.backup(path) rescue StandardError => detail Puppet.warning detail end end end @report.add_resource_status(status) end finishtime = Time.now @report.add_times("inspect", finishtime - inspect_starttime) @report.finalize_report begin Puppet::Transaction::Report.indirection.save(@report) rescue => detail Puppet.log_exception(detail, "Could not send report: #{detail}") end end end end puppet-3.8.5/lib/puppet/application/instrumentation_data.rb0000664005276200011600000000057312650174557024126 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::Instrumentation_data < Puppet::Application::IndirectionBase end puppet-3.8.5/lib/puppet/application/instrumentation_listener.rb0000664005276200011600000000057712650174557025046 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::Instrumentation_listener < Puppet::Application::IndirectionBase end puppet-3.8.5/lib/puppet/application/instrumentation_probe.rb0000664005276200011600000000057412650174557024325 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::Instrumentation_probe < Puppet::Application::IndirectionBase end puppet-3.8.5/lib/puppet/application/key.rb0000664005276200011600000000017112650174557020454 0ustar jenkinsjenkinsrequire 'puppet/application/indirection_base' class Puppet::Application::Key < Puppet::Application::IndirectionBase end puppet-3.8.5/lib/puppet/application/kick.rb0000664005276200011600000002240312650174557020607 0ustar jenkinsjenkinsrequire 'puppet/application' class Puppet::Application::Kick < Puppet::Application attr_accessor :hosts, :tags, :classes option("--all","-a") option("--foreground","-f") option("--debug","-d") option("--ping","-P") option("--test") option("--ignoreschedules") option("--host HOST") do |arg| @hosts << arg end option("--tag TAG", "-t") do |arg| @tags << arg end option("--class CLASS", "-c") do |arg| @classes << arg end option("--no-fqdn", "-n") do |arg| options[:fqdn] = false end option("--parallel PARALLEL", "-p") do |arg| begin options[:parallel] = Integer(arg) rescue $stderr.puts "Could not convert #{arg.inspect} to an integer" exit(23) end end def help <<-'HELP' puppet-kick(8) -- Remotely control puppet agent ======== SYNOPSIS -------- Trigger a puppet agent run on a set of hosts. USAGE ----- puppet kick [-a|--all] [-c|--class ] [-d|--debug] [-f|--foreground] [-h|--help] [--host ] [--no-fqdn] [--ignoreschedules] [-t|--tag ] [--test] [-p|--ping] [ [...]] DESCRIPTION ----------- This script can be used to connect to a set of machines running 'puppet agent' and trigger them to run their configurations. The most common usage would be to specify a class of hosts and a set of tags, and 'puppet kick' would look up in LDAP all of the hosts matching that class, then connect to each host and trigger a run of all of the objects with the specified tags. If you are not storing your host configurations in LDAP, you can specify hosts manually. You will most likely have to run 'puppet kick' as root to get access to the SSL certificates. 'puppet kick' reads 'puppet master''s configuration file, so that it can copy things like LDAP settings. USAGE NOTES ----------- Puppet kick needs the puppet agent on the target machine to be running as a daemon, be configured to listen for incoming network connections, and have an appropriate security configuration. The specific changes required are: * Set `listen = true` in the agent's `puppet.conf` file (or `--listen` on the command line) * Configure the node's firewall to allow incoming connections on port 8139 * Insert the following stanza at the top of the node's `auth.conf` file: # Allow puppet kick access path /run method save auth any allow workstation.example.com This example would allow the machine `workstation.example.com` to trigger a Puppet run; adjust the "allow" directive to suit your site. You may also use `allow *` to allow anyone to trigger a Puppet run, but that makes it possible to interfere with your site by triggering excessive Puppet runs. See `http://docs.puppetlabs.com/guides/rest_auth_conf.html` for more details about security settings. 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 http://docs.puppetlabs.com/references/latest/configuration.html for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet master with '--genconfig'. * --all: Connect to all available hosts. Requires LDAP support at this point. * --class: Specify a class of machines to which to connect. This only works if you have LDAP configured, at the moment. * --debug: Enable full debugging. * --foreground: Run each configuration in the foreground; that is, when connecting to a host, do not return until the host has finished its run. The default is false. * --help: Print this help message * --host: A specific host to which to connect. This flag can be specified more than once. * --ignoreschedules: Whether the client should ignore schedules when running its configuration. This can be used to force the client to perform work it would not normally perform so soon. The default is false. * --parallel: How parallel to make the connections. Parallelization is provided by forking for each client to which to connect. The default is 1, meaning serial execution. * --puppetport: Use the specified TCP port to connect to agents. Defaults to 8139. * --tag: Specify a tag for selecting the objects to apply. Does not work with the --test option. * --test: Print the hosts you would connect to but do not actually connect. This option requires LDAP support at this point. * --ping: Do an ICMP echo against the target host. Skip hosts that don't respond to ping. EXAMPLE ------- $ sudo puppet kick -p 10 -t remotefile -t webserver host1 host2 AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end def run_command @hosts += command_line.args options[:test] ? test : main end def test puts "Skipping execution in test mode" exit(0) end def main Puppet.warning "Failed to load ruby LDAP library. LDAP functionality will not be available" unless Puppet.features.ldap? require 'puppet/util/ldap/connection' todo = @hosts.dup failures = [] # Now do the actual work go = true while go # If we don't have enough children in process and we still have hosts left to # do, then do the next host. if @children.length < options[:parallel] and ! todo.empty? host = todo.shift pid = safe_posix_fork do run_for_host(host) end @children[pid] = host else # Else, see if we can reap a process. begin pid = Process.wait if host = @children[pid] # Remove our host from the list of children, so the parallelization # continues working. @children.delete(pid) failures << host if $CHILD_STATUS.exitstatus != 0 print "#{host} finished with exit code #{$CHILD_STATUS.exitstatus}\n" else $stderr.puts "Could not find host for PID #{pid} with status #{$CHILD_STATUS.exitstatus}" end rescue Errno::ECHILD # There are no children left, so just exit unless there are still # children left to do. next unless todo.empty? if failures.empty? puts "Finished" exit(0) else puts "Failed: #{failures.join(", ")}" exit(3) end end end end end def run_for_host(host) if options[:ping] %x{ping -c 1 #{host}} unless $CHILD_STATUS == 0 $stderr.print "Could not contact #{host}\n" exit($CHILD_STATUS) end end require 'puppet/run' Puppet::Run.indirection.terminus_class = :rest port = Puppet[:puppetport] url = ["https://#{host}:#{port}", "production", "run", host].join('/') print "Triggering #{host}\n" begin run_options = { :tags => @tags, :background => ! options[:foreground], :ignoreschedules => options[:ignoreschedules] } run = Puppet::Run.indirection.save(Puppet::Run.new( run_options ), url) puts "Getting status" result = run.status puts "status is #{result}" rescue => detail Puppet.log_exception(detail, "Host #{host} failed: #{detail}\n") exit(2) end case result when "success"; exit(0) when "running" $stderr.puts "Host #{host} is already running" exit(3) else $stderr.puts "Host #{host} returned unknown answer '#{result}'" exit(12) end end def initialize(*args) super @hosts = [] @classes = [] @tags = [] end def preinit [:INT, :TERM].each do |signal| Signal.trap(signal) do $stderr.puts "Cancelling" exit(1) end end options[:parallel] = 1 options[:verbose] = true options[:fqdn] = true options[:ignoreschedules] = false options[:foreground] = false end def setup super() raise Puppet::Error.new("Puppet kick is not supported on Microsoft Windows") if Puppet.features.microsoft_windows? Puppet.warning "Puppet kick is deprecated. See http://links.puppetlabs.com/puppet-kick-deprecation" if options[:debug] Puppet::Util::Log.level = :debug else Puppet::Util::Log.level = :info end if Puppet[:node_terminus] == :ldap and (options[:all] or @classes) if options[:all] @hosts = Puppet::Node.indirection.search("whatever", :fqdn => options[:fqdn]).collect { |node| node.name } puts "all: #{@hosts.join(", ")}" else @hosts = [] @classes.each do |klass| list = Puppet::Node.indirection.search("whatever", :fqdn => options[:fqdn], :class => klass).collect { |node| node.name } puts "#{klass}: #{list.join(", ")}" @hosts += list end end elsif ! @classes.empty? $stderr.puts "You must be using LDAP to specify host classes" exit(24) end @children = {} # If we get a signal, then kill all of our children and get out. [:INT, :TERM].each do |signal| Signal.trap(signal) do Puppet.notice "Caught #{signal}; shutting down" @children.each do |pid, host| Process.kill("INT", pid) end waitall exit(1) end end end end puppet-3.8.5/lib/puppet/application/man.rb0000664005276200011600000000015312650174557020437 0ustar jenkinsjenkinsrequire 'puppet/application/face_base' class Puppet::Application::Man < Puppet::Application::FaceBase end puppet-3.8.5/lib/puppet/application/master.rb0000664005276200011600000002046112650174557021163 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 help <<-'HELP' puppet-master(8) -- The puppet master daemon ======== 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 http://docs.puppetlabs.com/references/stable/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. (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 Labs, LLC Licensed under the Apache 2.0 License HELP end # Sets up the 'node_cache_terminus' default to use the Write Only Yaml terminus :write_only_yaml. # If this is not wanted, the setting ´node_cache_terminus´ should be set to nil. # @see Puppet::Node::WriteOnlyYaml # @see #setup_node_cache # @see puppet issue 16753 # def app_defaults super.merge({ :node_cache_terminus => :write_only_yaml, :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 #{options[:node]}" end puts PSON::pretty_generate(catalog.to_resource, :allow_nan => true, :max_nesting => false) rescue => detail Puppet.log_exception(detail, "Failed to compile catalog for node #{options[:node]}: #{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? begin Puppet::Util.chuser rescue => detail Puppet.log_exception(detail, "Could not change user to #{Puppet[:user]}: #{detail}") exit(39) end end if options[:rack] start_rack_master else start_webrick_master end end def setup_logs set_log_level if !options[:setdest] if options[:node] # We are compiling a catalog for a single node with '--compile' and logging # has not already been configured via '--logdest' so log to the console. Puppet::Util::Log.newdestination(:console) elsif !(Puppet[:daemonize] or options[:rack]) # We are running a webrick master which has been explicitly foregrounded # and '--logdest' has not been passed, 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' 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 end # Sets up a special node cache "write only yaml" that collects and stores node data in yaml # but never finds or reads anything (this since a real cache causes stale data to be served # in circumstances when the cache can not be cleared). # @see puppet issue 16753 # @see Puppet::Node::WriteOnlyYaml # @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] 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 #{Puppet.version}" end end puppet-3.8.5/lib/puppet/application/module.rb0000664005276200011600000000015612650174557021154 0ustar jenkinsjenkinsrequire 'puppet/application/face_base' class Puppet::Application::Module < Puppet::Application::FaceBase end puppet-3.8.5/lib/puppet/application/node.rb0000664005276200011600000000017212650174557020612 0ustar jenkinsjenkinsrequire 'puppet/application/indirection_base' class Puppet::Application::Node < Puppet::Application::IndirectionBase end puppet-3.8.5/lib/puppet/application/parser.rb0000664005276200011600000000020412650174557021155 0ustar jenkinsjenkinsrequire 'puppet/application/face_base' require 'puppet/face' class Puppet::Application::Parser < Puppet::Application::FaceBase end puppet-3.8.5/lib/puppet/application/plugin.rb0000664005276200011600000000015512650174557021164 0ustar jenkinsjenkinsrequire 'puppet/application/face_base' class Puppet::Application::Plugin < Puppet::Application::FaceBase end puppet-3.8.5/lib/puppet/application/queue.rb0000664005276200011600000001034612650174557021015 0ustar jenkinsjenkinsrequire 'puppet/application' require 'puppet/util' require 'puppet/daemon' require 'puppet/util/pidlock' class Puppet::Application::Queue < Puppet::Application attr_accessor :daemon def app_defaults() super.merge( :pidfile => "$rundir/queue.pid" ) end def preinit @argv = ARGV.dup # Do an initial trap, so that cancels don't get a stack trace. # This exits with exit code 1 Signal.trap(:INT) do $stderr.puts "Caught SIGINT; shutting down" exit(1) end # This is a normal shutdown, so code 0 Signal.trap(:TERM) do $stderr.puts "Caught SIGTERM; shutting down" exit(0) end { :verbose => false, :debug => false }.each do |opt,val| options[opt] = val end end option("--debug","-d") option("--verbose","-v") def help <<-HELP puppet-queue(8) -- Deprecated queuing daemon for asynchronous storeconfigs ======== SYNOPSIS -------- Retrieves serialized storeconfigs records from a queue and processes them in order. THIS FEATURE IS DEPRECATED; use PuppetDB instead. USAGE ----- puppet queue [-d|--debug] [--help] [-v|--verbose] [--version] DESCRIPTION ----------- This application runs as a daemon and processes storeconfigs data, retrieving the data from a stomp server message queue and writing it to a database. It was once necessary as a workaround for the poor performance of ActiveRecord-based storeconfigs, but has been supplanted by the PuppetDB service, which gives better performance with less complexity. For more information, see the PuppetDB documentation at http://docs.puppetlabs.com/puppetdb/latest OPTIONS ------- Note that any 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. See the configuration file documentation at http://docs.puppetlabs.com/references/stable/configuration.html for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet queue with '--genconfig'. * --debug: Enable full debugging. * --help: Print this help message * --verbose: Turn on verbose reporting. * --version: Print the puppet version number and exit. EXAMPLE ------- $ puppet queue AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end option("--logdest DEST", "-l DEST") do |arg| handle_logdest_arg(arg) end def main require 'puppet/indirector/catalog/queue' # provides Puppet::Indirector::Queue.subscribe Puppet.notice "Starting puppet queue #{Puppet.version}" Puppet::Resource::Catalog::Queue.subscribe do |catalog| # Once you have a Puppet::Resource::Catalog instance, passing it to save should suffice # to put it through to the database via its active_record indirector (which is determined # by the terminus_class = :active_record setting above) Puppet::Util.benchmark(:notice, "Processing queued catalog for #{catalog.name}") do begin Puppet::Resource::Catalog.indirection.save(catalog) rescue => detail Puppet.log_exception(detail, "Could not save queued catalog for #{catalog.name}: #{detail}") end end end Thread.list.each { |thread| thread.join } end def setup Puppet.warning "Puppet queue is deprecated. See http://links.puppetlabs.com/puppet-queue-deprecation" unless Puppet.features.stomp? raise ArgumentError, "Could not load the 'stomp' library, which must be present for queueing to work. You must install the required library." end setup_logs exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? require 'puppet/resource/catalog' Puppet::Resource::Catalog.indirection.terminus_class = :store_configs daemon = Puppet::Daemon.new(Puppet::Util::Pidlock.new(Puppet[:pidfile])) daemon.argv = @argv daemon.daemonize if Puppet[:daemonize] # We want to make sure that we don't have a cache # class set up, because if storeconfigs is enabled, # we'll get a loop of continually caching the catalog # for storage again. Puppet::Resource::Catalog.indirection.cache_class = nil end end puppet-3.8.5/lib/puppet/application/report.rb0000664005276200011600000000017412650174557021202 0ustar jenkinsjenkinsrequire 'puppet/application/indirection_base' class Puppet::Application::Report < Puppet::Application::IndirectionBase end puppet-3.8.5/lib/puppet/application/resource.rb0000664005276200011600000001365712650174557021530 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("--host HOST","-H") do |arg| Puppet.warning("Accessing resources on the network is deprecated. See http://links.puppetlabs.com/deprecate-networked-resource") @host = arg end 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 end option("--param PARAM", "-p") do |arg| @extra_params << arg.to_sym end def help <<-'HELP' puppet-resource(8) -- The resource abstraction layer shell ======== SYNOPSIS -------- Uses the Puppet RAL to directly interact with the system. USAGE ----- puppet resource [-h|--help] [-d|--debug] [-v|--verbose] [-e|--edit] [-H|--host ] [-p|--param ] [-t|--types] [] [= ...] 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 http://docs.puppetlabs.com/references/stable/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. * --host: When specified, connect to the resource server on the named host and retrieve the list of resouces of the type specified. * --help: Print this help message. * --param: Add more parameters to be outputted from queries. * --types: List all available types. * --verbose: Print extra information. 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 Labs, LLC Licensed under the Apache 2.0 License HELP end def main type, name, params = parse_args(command_line.args) raise "You cannot edit a remote host" if options[:edit] and @host resources = find_or_save_resources(type, name, params) text = resources. map { |resource| resource.prune_parameters(:parameters_to_include => @extra_params).to_manifest }. join("\n") options[:edit] ? handle_editing(text) : (puts text) end def setup Puppet::Util::Log.newdestination(:console) set_log_level end private def remote_key(type, name) Puppet::Resource.indirection.terminus_class = :rest port = Puppet[:puppetport] ["https://#{@host}:#{port}", "production", "resources", type, name].join('/') end 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) 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}" name = args.shift params = {} args.each do |setting| if setting =~ /^(\w+)=(.+)$/ params[$1] = $2 else raise "Invalid parameter setting #{setting}" end end [type, name, params] end def find_or_save_resources(type, name, params) key = @host ? remote_key(type, name) : 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-3.8.5/lib/puppet/application/resource_type.rb0000664005276200011600000000056412650174557022562 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::Resource_type < Puppet::Application::IndirectionBase end puppet-3.8.5/lib/puppet/application/secret_agent.rb0000664005276200011600000000061512650174557022332 0ustar jenkinsjenkinsrequire 'puppet/application/face_base' require 'puppet/face' # 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::Secret_agent < Puppet::Application::FaceBase run_mode :agent end puppet-3.8.5/lib/puppet/application/status.rb0000664005276200011600000000017412650174557021212 0ustar jenkinsjenkinsrequire 'puppet/application/indirection_base' class Puppet::Application::Status < Puppet::Application::IndirectionBase end puppet-3.8.5/lib/puppet/bindings.rb0000664005276200011600000001261512650174557017164 0ustar jenkinsjenkins# This class allows registration of named bindings that are later contributed to a layer via # a binding scheme. # # The intended use is for a .rb file to be placed in confdir's or module's `lib/bindings` directory structure, with a # name corresponding to the symbolic bindings name. # # Here are two equivalent examples, the first using chained methods (which is compact for simple cases), and the # second which uses a block. # # @example MyModule's lib/bindings/mymodule/default.rb # Puppet::Bindings.newbindings('mymodule::default') do # bind.integer.named('meaning of life').to(42) # end # # @example Using blocks # Puppet::Bindings.newbindings('mymodule::default') do # bind do # integer # name 'meaning of life' # to 42 # end # end # # If access is needed to the scope, this can be declared as a block parameter. # @example MyModule's lib/bindings/mymodule/default.rb with scope # Puppet::Bindings.newbindings('mymodule::default') do |scope| # bind.integer.named('meaning of life').to("#{scope['::fqdn']} also think it is 42") # end # # If late evaluation is wanted, this can be achieved by binding a puppet expression. # @example binding a puppet expression # Puppet::Bindings.newbindings('mymodule::default') do |scope| # bind.integer.named('meaning of life').to(puppet_string("${::fqdn} also think it is 42") # end # # It is allowed to define methods in the block given to `newbindings`, these can be used when # producing bindings. (Care should naturally be taken to not override any of the already defined methods). # @example defining method to be used while creating bindings # Puppet::Bindings.newbindings('mymodule::default') do # def square(x) # x * x # end # bind.integer.named('meaning of life squared').to(square(42)) # end # # For all details see {Puppet::Pops::Binder::BindingsFactory}, which is used behind the scenes. # @api public # class Puppet::Bindings extend Enumerable Environment = Puppet::Node::Environment # Constructs and registers a {Puppet::Pops::Binder::Bindings::NamedBindings NamedBindings} that later can be contributed # to a bindings layer in a bindings configuration via a URI. The name is symbolic, fully qualified with module name, and at least one # more qualifying name (where the name `default` is used in the default bindings configuration. # # The given block is called with a `self` bound to an instance of {Puppet::Pops::Binder::BindingsFactory::BindingsContainerBuilder} # which most notably has a `#bind` method which it turn calls a block bound to an instance of # {Puppet::Pops::Binder::BindingsFactory::BindingsBuilder}. # Depending on the use-case a direct chaining method calls or nested blocks may be used. # # @example simple bindings # Puppet::Bindings.newbindings('mymodule::default') do # bind.name('meaning of life').to(42) # bind.integer.named('port').to(8080) # bind.integer.named('apache::port').to(8080) # end # # The block form is more suitable for longer, more complex forms of bindings. # def self.newbindings(name, &block) register_proc(name, block) end def self.register_proc(name, block) adapter = NamedBindingsAdapter.adapt(Puppet.lookup(:current_environment)) adapter[name] = block end # Registers a named_binding under its name # @param named_bindings [Puppet::Pops::Binder::Bindings::NamedBindings] The named bindings to register. # @api public # def self.register(named_bindings) adapter = NamedBindingsAdapter.adapt(Puppet.lookup(:current_environment)) adapter[named_bindings.name] = named_bindings end def self.resolve(scope, name) entry = get(name) return entry unless entry.is_a?(Proc) named_bindings = Puppet::Pops::Binder::BindingsFactory.safe_named_bindings(name, scope, &entry).model adapter = NamedBindingsAdapter.adapt(Puppet.lookup(:current_environment)) adapter[named_bindings.name] = named_bindings named_bindings end # Returns the named bindings with the given name, or nil if no such bindings have been registered. # @param name [String] The fully qualified name of a binding to get # @return [Proc, Puppet::Pops::Binder::Bindings::NamedBindings] a Proc producing named bindings, or a named bindings directly # @api public # def self.get(name) adapter = NamedBindingsAdapter.adapt(Puppet.lookup(:current_environment)) adapter[name] end def self.[](name) get(name) end # Supports Enumerable iteration (k,v) over the named bindings hash. def self.each adapter = NamedBindingsAdapter.adapt(Puppet.lookup(:current_environment)) adapter.each_pair {|k,v| yield k,v } end # A NamedBindingsAdapter holds a map of name to Puppet::Pops::Binder::Bindings::NamedBindings. # It is intended to be used as an association between an Environment and named bindings. # class NamedBindingsAdapter < Puppet::Pops::Adaptable::Adapter def initialize() @named_bindings = {} end def [](name) @named_bindings[name] end def has_name?(name) @named_bindings.has_key? end def []=(name, value) unless value.is_a?(Puppet::Pops::Binder::Bindings::NamedBindings) || value.is_a?(Proc) raise ArgumentError, "Given value must be a NamedBindings, or a Proc producing one, got: #{value.class}." end @named_bindings[name] = value end def each_pair(&block) @named_bindings.each_pair(&block) end end end puppet-3.8.5/lib/puppet/coercion.rb0000664005276200011600000000167412650174557017173 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-3.8.5/lib/puppet/configurer.rb0000664005276200011600000002503512650174557017532 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' require 'puppet/configurer/downloader_factory' 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 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 #{Puppet[:statefile]}: #{detail}") begin Puppet::FileSystem.unlink(Puppet[:statefile]) retry rescue => detail raise Puppet::Error.new("Cannot remove #{Puppet[:statefile]}: #{detail}", detail) end end def initialize(factory = Puppet::Configurer::DownloaderFactory.new) Puppet.settings.use(:main, :ssl, :agent) @running = false @splayed = false @environment = Puppet[:environment] @transaction_uuid = SecureRandom.uuid @handler = Puppet::Configurer::PluginHandler.new(factory) end # Get the remote catalog, yo. Returns nil if no catalog can be found. def retrieve_catalog(query_options) query_options ||= {} # First try it with no cache, then with the cache. unless (Puppet[:use_cached_catalog] and result = retrieve_catalog_from_cache(query_options)) or result = retrieve_new_catalog(query_options) if ! Puppet[:usecacheonfailure] Puppet.warning "Not using cache on failed catalog" return nil end result = retrieve_catalog_from_cache(query_options) end return nil unless result convert_catalog(result, @duration) end # Convert a plain resource catalog into our full host catalog. def convert_catalog(result, duration) catalog = result.to_ral catalog.finalize catalog.retrieval_duration = duration catalog.write_class_file catalog.write_resource_file catalog end def get_facts(options) if options[:pluginsync] remote_environment_for_plugins = Puppet::Node::Environment.remote(@environment) download_plugins(remote_environment_for_plugins) 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 facts_hash = facts_for_uploading 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] unless catalog = (options.delete(:catalog) || retrieve_catalog(query_options)) Puppet.err "Could not retrieve catalog; skipping run" return end catalog end # Retrieve (optionally) and apply a catalog. If a catalog is passed in # the options, then apply that one, otherwise retrieve it. def apply_catalog(catalog, options) report = options[:report] report.configuration_version = catalog.version benchmark(:notice, "Finished catalog run") do catalog.apply(options) end report.finalize_report 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]) begin Puppet.override(:http_pool => pool) do run_internal(options) end ensure pool.close end end def run_internal(options) # 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("apply", nil, @environment, @transaction_uuid) report = options[:report] init_storage Puppet::Util::Log.newdestination(report) begin unless Puppet[:node_name_fact].empty? query_options = get_facts(options) end # We only need to find out the environment to run in if we don't already have a catalog unless options[:catalog] begin if node = Puppet::Node.indirection.find(Puppet[:node_name_value], :environment => Puppet::Node::Environment.remote(@environment), :ignore_cache => true, :transaction_uuid => @transaction_uuid, :fail_on_404 => true) # 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 if node.environment.to_s != @environment Puppet.warning "Local environment: \"#{@environment}\" doesn't match server specified node environment \"#{node.environment}\", switching agent to \"#{node.environment}\"." @environment = node.environment.to_s report.environment = @environment query_options = nil end end rescue SystemExit,NoMemoryError raise rescue Exception => 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) local_node_environment = if current_environment.name == @environment.intern current_environment else 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[:transaction_uuid] = @transaction_uuid unless catalog = prepare_and_retrieve_catalog(options, query_options) 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" end Puppet.warning "Local environment: \"#{@environment}\" doesn't match server specified environment \"#{catalog.environment}\", restarting agent run with environment \"#{catalog.environment}\"" @environment = catalog.environment report.environment = @environment query_options = get_facts(options) query_options[:transaction_uuid] = @transaction_uuid return nil unless catalog = prepare_and_retrieve_catalog(options, query_options) tries += 1 end execute_prerun_command or return nil apply_catalog(catalog, options) report.exit_status rescue => detail Puppet.log_exception(detail, "Failed to apply catalog: #{detail}") return nil ensure execute_postrun_command or return nil end ensure # Between Puppet runs we need to forget the cached values. This lets us # pick up on new functions installed by gems or new modules being added # without the daemon being restarted. $env_module_directories = nil Puppet::Util::Log.close(report) send_report(report) Puppet.pop_context end private :run_internal 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}") 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}") 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}") 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 Puppet.notice "Using cached catalog" result rescue => detail Puppet.log_exception(detail, "Could not retrieve catalog from cache: #{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, :environment => Puppet::Node::Environment.remote(@environment), :fail_on_404 => true)) end result rescue SystemExit,NoMemoryError raise rescue Exception => detail Puppet.log_exception(detail, "Could not retrieve catalog from remote server: #{detail}") return nil end def download_plugins(remote_environment_for_plugins) @handler.download_plugins(remote_environment_for_plugins) end end puppet-3.8.5/lib/puppet/configurer/0000775005276200011600000000000012650174565017177 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/configurer/downloader.rb0000664005276200011600000000316512650174557021670 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}" 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}") 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, :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-3.8.5/lib/puppet/configurer/downloader_factory.rb0000664005276200011600000000162512650174557023416 0ustar jenkinsjenkinsrequire 'puppet/configurer' # Factory for Puppet::Configurer::Downloader objects. # # Puppet's pluginsync facilities can be used to download modules # and external facts, each with a different destination directory # and related settings. # # @api private # class Puppet::Configurer::DownloaderFactory def create_plugin_downloader(environment) plugin_downloader = Puppet::Configurer::Downloader.new( "plugin", Puppet[:plugindest], Puppet[:pluginsource], Puppet[:pluginsignore], environment ) end def create_plugin_facts_downloader(environment) source_permissions = Puppet.features.microsoft_windows? ? :ignore : :use plugin_fact_downloader = Puppet::Configurer::Downloader.new( "pluginfacts", Puppet[:pluginfactdest], Puppet[:pluginfactsource], Puppet[:pluginsignore], environment, source_permissions ) end end puppet-3.8.5/lib/puppet/configurer/fact_handler.rb0000664005276200011600000000226712650174557022146 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 => @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}" Puppet.log_exception(detail, message) raise Puppet::Error, message, detail.backtrace end end def facts_for_uploading facts = find_facts text = facts.render(:pson) {:facts_format => :pson, :facts => CGI.escape(text)} end end puppet-3.8.5/lib/puppet/configurer/plugin_handler.rb0000664005276200011600000000123112650174557022515 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 def initialize(factory) @factory = factory end # Retrieve facts from the central server. def download_plugins(environment) plugin_downloader = @factory.create_plugin_downloader(environment) if Puppet.features.external_facts? plugin_fact_downloader = @factory.create_plugin_facts_downloader(environment) plugin_fact_downloader.evaluate end plugin_downloader.evaluate Puppet::Util::Autoload.reload_changed end end puppet-3.8.5/lib/puppet/confine.rb0000664005276200011600000000303412650174557017003 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[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 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-3.8.5/lib/puppet/confine/0000775005276200011600000000000012650174565016455 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/confine/any.rb0000664005276200011600000000075412650174557017600 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-3.8.5/lib/puppet/confine/exists.rb0000664005276200011600000000072312650174557020324 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-3.8.5/lib/puppet/confine/false.rb0000664005276200011600000000054112650174557020075 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-3.8.5/lib/puppet/confine/feature.rb0000664005276200011600000000057712650174557020447 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-3.8.5/lib/puppet/confine/true.rb0000664005276200011600000000062412650174557017764 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-3.8.5/lib/puppet/confine/variable.rb0000664005276200011600000000315612650174557020575 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-3.8.5/lib/puppet/confine_collection.rb0000664005276200011600000000234612650174557021223 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-3.8.5/lib/puppet/confiner.rb0000664005276200011600000000332212650174557017165 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-3.8.5/lib/puppet/context.rb0000664005276200011600000000533312650174557017052 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 @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) value = @table[name] value.is_a?(Proc) ? (@table[name] = value.call) : value elsif block block.call else raise UndefinedBindingError, "no '#{name}' in #{@table.inspect} at top of #{@stack.inspect}" 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 # 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" 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}'" end while @stack[-1][0] != @rollbacks[name] pop end @rollbacks.delete(name) end end puppet-3.8.5/lib/puppet/context/0000775005276200011600000000000012650174565016520 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/context/trusted_information.rb0000664005276200011600000000306312650174557023147 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 def initialize(authenticated, certname, extensions) @authenticated = authenticated.freeze @certname = certname.freeze @extensions = extensions.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 }.freeze end end puppet-3.8.5/lib/puppet/daemon.rb0000664005276200011600000001227512650174557016634 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 if agent.running? Puppet.notice "Not triggering already-running agent" return end agent.run({:splay => false}) 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 set_signal_traps 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-3.8.5/lib/puppet/data_binding.rb0000664005276200011600000000055212650174557017767 0ustar jenkinsjenkinsrequire 'puppet/indirector' # A class for managing data lookups class Puppet::DataBinding class LookupError < Puppet::Error; end # 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.") end puppet-3.8.5/lib/puppet/defaults.rb0000664005276200011600000025321212650174557017176 0ustar jenkinsjenkinsmodule Puppet def self.default_diffargs if (Facter.value(:kernel) == "AIX" && Facter.value(:kernelmajversion) == "5300") "" else "-u" end 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).} STORECONFIGS_ONLY = %q{This setting is only used by the ActiveRecord storeconfigs and inventory backends, which are deprecated.} # This is defined first so that the facter implementation is replaced before other setting defaults are evaluated. define_settings(:main, :cfacter => { :default => false, :type => :boolean, :desc => 'Whether or not to use the native facter (cfacter) implementation instead of the Ruby one (facter). Defaults to false.', :hook => proc do |value| return unless value raise ArgumentError, 'facter has already evaluated facts.' if Facter.instance_variable_get(:@collection) raise ArgumentError, 'cfacter version 0.2.0 or later is not installed.' unless Puppet.features.cfacter? CFacter.initialize end } ) 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.", }, :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 ", }, :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.", :hook => proc do |value| values = munge(value) valid = %w[deprecations] invalid = values - (values & valid) if not invalid.empty? raise ArgumentError, "Cannot disable unrecognized warning types #{invalid.inspect}. Valid values are #{valid.inspect}." 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", }, :profile => { :default => false, :type => :boolean, :desc => "Whether to enable experimental performance profiling", }, :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 => "", :desc => "Print 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'.", }, :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 => true, :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.", :call_hook => :on_define_and_write, :hook => proc do |value| ENV["PATH"] = "" if ENV["PATH"].nil? ENV["PATH"] = value unless value == "none" paths = ENV["PATH"].split(File::PATH_SEPARATOR) Puppet::Util::Platform.default_paths.each do |path| 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) and $LOAD_PATH.include?(@oldlibdir) @oldlibdir = value $LOAD_PATH << value end }, :ignoreimport => { :default => false, :type => :boolean, :desc => "If true, allows the parser to continue without requiring all files referenced with `import` statements to exist. This setting was primarily designed for use with commit hooks for parse-checking.", }, :environment => { :default => "production", :desc => "The environment Puppet is running in. For clients (e.g., `puppet agent`) this determines the environment itself, which is used to find modules and much more. For servers (i.e., `puppet master`) this provides the default environment for nodes we know nothing about." }, :environmentpath => { :default => "", :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 `$confdir/environments`. For more details, see http://docs.puppetlabs.com/puppet/latest/reference/environments.html", :type => :path, }, :always_cache_features => { :type => :boolean, :default => false, :desc => <<-'EOT' Affects how we cache attempts to load Puppet 'features'. If false, then calls to `Puppet.features.?` will always attempt to load the 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 feature, and then later load that feature during the same run (even if the feature had been tested earlier and had not been available). If this setting is set to true, then 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 feature. This behavior is almost always appropriate for the server, and can result in a significant performance improvement for 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 => "Where to find information about nodes.", }, :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'). The master application defaults to 'write_only_yaml', all others to none.", }, :data_binding_terminus => { :type => :terminus, :default => "hiera", :desc => "Where to retrive information about data.", }, :hiera_config => { :default => "$confdir/hiera.yaml", :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 => { :default => false, :desc => "Turns the binding system on or off. This includes bindings in modules. The binding system aggregates data from modules and other locations and makes them available for lookup. The binding system is experimental and any or all of it may change.", :type => :boolean, }, :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.", :call_hook => :on_initialize_and_write, :hook => proc do |value| require 'puppet/node/facts' # Cache to YAML if we're uploading facts away if %w[rest inventory_service].include? value.to_s Puppet.info "configuring the YAML fact cache because a remote terminus is active" Puppet::Node::Facts.indirection.cache_class = :yaml end end }, :inventory_terminus => { :type => :terminus, :default => "$facts_terminus", :desc => "Should usually be the same as the 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." }, :httplog => { :default => "$logdir/http.log", :type => :file, :owner => "root", :mode => "0640", :desc => "Where the puppet agent web server logs.", }, :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." }, :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." }, :queue_type => { :default => "stomp", :desc => "Which type of queue to use for asynchronous processing.", }, :queue_source => { :default => "stomp://localhost:61613/", :desc => "Which type of queue to use for asynchronous processing. If your stomp server requires authentication, you can include it in the URI as long as your stomp client library is at least 1.1.1", }, :async_storeconfigs => { :default => false, :type => :boolean, :desc => "Whether to use a queueing system to provide asynchronous database integration. Requires that `puppet queue` be running.", :hook => proc do |value| if value # This reconfigures the termini for Node, Facts, and Catalog Puppet.settings.override_default(:storeconfigs, true) # But then we modify the configuration Puppet::Resource::Catalog.indirection.cache_class = :queue Puppet.settings.override_default(:catalog_cache_terminus, :queue) else raise "Cannot disable asynchronous storeconfigs in a running process" end end }, :thin_storeconfigs => { :default => false, :type => :boolean, :desc => "Boolean; whether Puppet should store only facts and exported resources in the storeconfigs database. This will improve the performance of exported resources with the older `active_record` backend, but will disable external tools that search the storeconfigs database. Thinning catalogs is generally unnecessary when using PuppetDB to store catalogs.", :hook => proc do |value| Puppet.settings.override_default(:storeconfigs, true) if value end }, :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 deprecated. Please set a per-environment value in environment.conf instead. For more info, see http://docs.puppetlabs.com/puppet/latest/reference/environments.html", :deprecated => :allowed_on_commandline, }, :zlib => { :default => true, :type => :boolean, :desc => "Boolean; whether to use the zlib library", }, :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.", }, :stringify_facts => { :default => true, :type => :boolean, :desc => "Flatten fact values to strings using #to_s. Means you can't have arrays or hashes as fact values. (DEPRECATED) This option will be removed in Puppet 4.0.", }, :trusted_node_data => { :default => false, :type => :boolean, :desc => "Stores trusted node data in a hash called $trusted. When true also prevents $trusted from being overridden in any scope.", }, :immutable_node_data => { :default => '$trusted_node_data', :type => :boolean, :desc => "When true, also prevents $trusted and $facts from being overridden in any scope", }, :preview_outputdir => { :default => '$vardir/preview', :type => :directory, :mode => "0750", :owner => "service", :group => "service", :desc => "The directory where catalog previews per node are generated." } ) Puppet.define_settings(:module_tool, :module_repository => { :default => 'https://forgeapi.puppetlabs.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", } ) Puppet.define_settings( :main, # We have to downcase the fqdn, because the current ssl stuff (as oppsed 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](http://docs.puppetlabs.com/puppet/latest/reference/config_file_auth.html). In most cases, it is also used as the node's name when matching [node definitions](http://docs.puppetlabs.com/puppet/latest/reference/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](http://docs.puppetlabs.com/puppet/latest/reference/lang_facts_and_builtin_vars.html) for more details.) * For best compatibility, you should limit the value of `certname` to only use 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; see #1168") unless value == value.downcase }}, :certdnsnames => { :default => '', :hook => proc do |value| unless value.nil? or value == '' then Puppet.warning < < { :default => '', :desc => < { :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 => true, :type => :boolean, :desc => "Whether certificate revocation should be supported by downloading a Certificate Revocation List (CRL) to all clients. If enabled, CA chaining will almost definitely not work.", }, :certificate_expire_warning => { :default => "60d", :type => :duration, :desc => "The window of time leading up to a certificate's expiration that a notification will be logged. This applies to CA, master, and agent certificates. #{AS_DURATION}" }, :digest_algorithm => { :default => 'md5', :type => :enum, :values => ["md5", "sha256"], :desc => 'Which digest algorithm to use for file resources and the filebucket. Valid values are md5, sha256. Default is md5.', } ) 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." }, :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." }, :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](http://docs.puppetlabs.com/guides/configuring.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}" }, :req_bits => { :default => 4096, :desc => "The bit length of the certificates.", }, :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.", }, :bindaddress => { :default => "0.0.0.0", :desc => "The address a listening server should bind to.", } ) define_settings(:master, :user => { :default => "puppet", :desc => "The user puppet master should run as.", }, :group => { :default => "puppet", :desc => "The group puppet master should run as.", }, :manifestdir => { :default => "$confdir/manifests", :type => :directory, :desc => "Used to build the default value of the `manifest` setting. Has no other purpose. This setting is deprecated.", :deprecated => :completely, }, :manifest => { :default => "$manifestdir/site.pp", :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 deprecated. 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 http://docs.puppetlabs.com/puppet/latest/reference/environments.html", :deprecated => :allowed_on_commandline, }, :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.", }, :masterlog => { :default => "$logdir/puppetmaster.log", :type => :file, :owner => "service", :group => "service", :mode => "0660", :desc => "This file is literally never used, although Puppet may create it as an empty file. For more context, see the `puppetdlog` setting and puppet master's `--logdest` command line option. This setting is deprecated and will be removed in a future version of Puppet.", :deprecated => :completely }, :masterhttplog => { :default => "$logdir/masterhttp.log", :type => :file, :owner => "service", :group => "service", :mode => "0660", :create => true, :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 port for puppet master traffic. For puppet master, this is the port to listen on; for puppet agent, this is the port to make requests on. Both applications use this setting to get the 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, :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`.", }, :ca => { :default => true, :type => :boolean, :desc => "Whether the master should function as a certificate authority.", }, :basemodulepath => { :default => "$confdir/modules#{File::PATH_SEPARATOR}/usr/share/puppet/modules", :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 ';'.) If you are using directory environments, 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 http://docs.puppetlabs.com/puppet/latest/reference/environments.html This setting also provides the default value for the deprecated `modulepath` setting, which is used when directory environments are disabled.", }, :modulepath => { :default => "$basemodulepath", :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 deprecated. 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 http://docs.puppetlabs.com/puppet/latest/reference/environments.html", :deprecated => :allowed_on_commandline, }, :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 inteface: 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 inteface: 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, tagmail`.) 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(:metrics, :rrddir => { :type => :directory, :default => "$vardir/rrd", :mode => "0750", :owner => "service", :group => "service", :desc => "The directory where RRD database files are stored. Directories for each reporting host will be created under this directory." }, :rrdinterval => { :default => "$runinterval", :type => :duration, :desc => "How often RRD should expect data. This should match how often the hosts report back to the server. #{AS_DURATION}", } ) define_settings(:device, :devicedir => { :default => "$vardir/devices", :type => :directory, :mode => "0750", :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.puppetlabs.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.puppetlabs.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 }, :localconfig => { :default => "$statedir/localconfig", :type => :file, :owner => "root", :mode => "0660", :desc => "Where puppet agent caches the local configuration. An extension indicating the cache format is added automatically."}, :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." }, :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 http://docs.puppetlabs.com/references/latest/man/." }, :server => { :default => "puppet", :desc => "The puppet master server to which the puppet agent should connect." }, :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.", }, :puppetport => { :default => 8139, :desc => "Which port puppet agent listens on.", }, :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](http://docs.puppetlabs.com/references/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}", }, :listen => { :default => false, :type => :boolean, :desc => "Whether puppet agent should listen for connections. If this is true, then puppet agent will accept incoming REST API requests, subject to the default ACLs and the ACLs set in the `rest_authconfig` file. Puppet agent can respond usefully to requests on the `run`, `facts`, `certificate`, and `resource` endpoints.", }, :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.", }, :catalog_format => { :default => "", :desc => "(Deprecated for 'preferred_serialization_format') What format to use to dump the catalog. Only supports 'marshal' and 'yaml'. Only matters on the client, since it asks the server for a specific format.", :hook => proc { |value| if value Puppet.deprecation_warning "Setting 'catalog_format' is deprecated; use 'preferred_serialization_format' instead." Puppet.settings.override_default(:preferred_serialization_format, value) end } }, :preferred_serialization_format => { :default => "pson", :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.", }, :report_serialization_format => { :default => "pson", :type => :enum, :values => ["pson", "yaml"], :desc => "The serialization format to use when sending reports to the `report_server`. Possible values are `pson` and `yaml`. This setting affects puppet agent, but not puppet apply (which processes its own reports). This should almost always be set to `pson`. It can be temporarily set to `yaml` to let agents using this Puppet version connect to a puppet master running Puppet 3.0.0 through 3.2.x. Note that this is set to 'yaml' automatically if the agent detects an older master, so should never need to be set explicitly." }, :legacy_query_parameter_serialization => { :default => false, :type => :boolean, :desc => "The serialization format to use when sending file_metadata query parameters. Older versions of puppet master expect certain query parameters to be serialized as yaml, which is deprecated. This should almost always be false. It can be temporarily set to true to let agents using this Puppet version connect to a puppet master running Puppet 3.0.0 through 3.2.x. Note that this is set to true automatically if the agent detects an older master, so should never need to be set explicitly." }, :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.", }, :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 => "Ignore cache and always recompile the configuration. This is useful for testing new configurations, where the local cache may in fact be stale even if the timestamps are up to date - if the facts change or if the server changes.", }, :dynamicfacts => { :default => "memorysize,memoryfree,swapsize,swapfree", :desc => "(Deprecated) Facts that are dynamic; these facts will be ignored when deciding whether changed facts should result in a recompile. Multiple facts should be comma-separated.", :hook => proc { |value| if value Puppet.deprecation_warning "The dynamicfacts setting is deprecated and will be ignored." end } }, :splaylimit => { :default => "$runinterval", :type => :duration, :desc => "The maximum time to delay before runs. Defaults to being the same as the run interval. #{AS_DURATION}", }, :splay => { :default => false, :type => :boolean, :desc => "Whether to sleep for a pseudo-random (but consistent) amount of time before a run.", }, :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 can help reduce flapping if too many clients contact the server at one time. #{AS_DURATION}", }, :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.", }, :inventory_server => { :default => "$server", :desc => "The server to send facts to.", }, :inventory_port => { :default => "$masterport", :desc => "The port to communicate with the inventory_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 for the different configuration graphs. These dot files can be interpreted by tools like OmniGraffle or dot (which is part of ImageMagick).", }, :graphdir => { :default => "$statedir/graphs", :type => :directory, :desc => "Where to store dot-outputted graphs.", }, :http_compression => { :default => false, :type => :boolean, :desc => "Allow http compression in REST communication with the master. This setting might improve performance for agent -> master communications over slow WANs. Your puppet master needs to support compression (usually by activating some settings in a reverse-proxy in front of the puppet master, which rules out webrick). It is harmless to activate this settings if your master doesn't support compression, but if it supports it, this setting might reduce performance on high-speed LANs.", }, :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 => "title-hash", :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. * `title-hash` (the default) will order resources randomly, but will use the same order across runs and across nodes. * `manifest` will use the order in which the resources were declared in their manifest files. * `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." } ) define_settings(:inspect, :archive_files => { :type => :boolean, :default => false, :desc => "During an inspect run, whether to archive files whose contents are audited to a file bucket.", }, :archive_file_server => { :default => "$server", :desc => "During an inspect run, the file bucket server to archive files to if archive_files is set.", } ) # 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://$server/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://$server/pluginfacts", :desc => "Where to retrieve external facts for pluginsync", }, :pluginsync => { :default => true, :type => :boolean, :desc => "Whether plugins should be synced with the central server.", }, :pluginsignore => { :default => ".svn CVS .git", :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( :tagmail, :tagmap => { :default => "$confdir/tagmail.conf", :desc => "The mapping between reporting tags and email addresses.", }, :sendmail => { :default => which('sendmail') || '', :desc => "Where to find the sendmail binary with which to send email.", }, :reportfrom => { :default => lambda { "report@#{Puppet::Settings.default_certname.downcase}" }, :desc => "The 'from' email address for the reports.", }, :smtpserver => { :default => "none", :desc => "The server through which to send email reports.", }, :smtpport => { :default => 25, :desc => "The TCP port through which to send email reports.", }, :smtphelo => { :default => lambda { Facter.value 'fqdn' }, :desc => "The name by which we identify ourselves in SMTP HELO for reports. If you send to a smtpserver which does strict HELO checking (as with Postfix's `smtpd_helo_restrictions` access controls), you may need to ensure this resolves.", } ) define_settings( :rails, :dblocation => { :default => "$statedir/clientconfigs.sqlite3", :type => :file, :mode => "0660", :owner => "service", :group => "service", :desc => "The sqlite database file. #{STORECONFIGS_ONLY}" }, :dbadapter => { :default => "sqlite3", :desc => "The type of database to use. #{STORECONFIGS_ONLY}", }, :dbmigrate => { :default => false, :type => :boolean, :desc => "Whether to automatically migrate the database. #{STORECONFIGS_ONLY}", }, :dbname => { :default => "puppet", :desc => "The name of the database to use. #{STORECONFIGS_ONLY}", }, :dbserver => { :default => "localhost", :desc => "The database server for caching. Only used when networked databases are used.", }, :dbport => { :default => "", :desc => "The database password for caching. Only used when networked databases are used. #{STORECONFIGS_ONLY}", }, :dbuser => { :default => "puppet", :desc => "The database user for caching. Only used when networked databases are used. #{STORECONFIGS_ONLY}", }, :dbpassword => { :default => "puppet", :desc => "The database password for caching. Only used when networked databases are used. #{STORECONFIGS_ONLY}", }, :dbconnections => { :default => '', :desc => "The number of database connections for networked databases. Will be ignored unless the value is a positive integer. #{STORECONFIGS_ONLY}", }, :dbsocket => { :default => "", :desc => "The database socket location. Only used when networked databases are used. Will be ignored if the value is an empty string. #{STORECONFIGS_ONLY}", }, :railslog => { :default => "$logdir/rails.log", :type => :file, :mode => "0600", :owner => "service", :group => "service", :desc => "Where Rails-specific logs are sent. #{STORECONFIGS_ONLY}" }, :rails_loglevel => { :default => "info", :desc => "The log level for Rails connections. The value must be a valid log level within Rails. Production environments normally use `info` and other environments normally use `debug`. #{STORECONFIGS_ONLY}", } ) define_settings( :couchdb, :couchdb_url => { :default => "http://127.0.0.1:5984/puppet", :desc => "The url where the puppet couchdb database will be created. Only used when `facts_terminus` is set to `couch`.", } ) 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.", }, :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 => "An external command that can produce node information. The command's output must be a YAML dump of a hash, and that hash must have a `classes` key and/or a `parameters` key, where `classes` is an array or hash and `parameters` is a hash. For unknown nodes, the command should exit with a non-zero exit code. This command makes it straightforward to store your node mapping information in other data sources like databases.", } ) 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 ActiveRecord and an SQL database to store and query the data; this, in turn, will depend on Rails being available. 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 if not Puppet.settings[:async_storeconfigs] Puppet::Resource::Catalog.indirection.cache_class = :store_configs Puppet.settings.override_default(:catalog_cache_terminus, :store_configs) end Puppet::Node::Facts.indirection.cache_class = :store_configs Puppet::Resource.indirection.terminus_class = :store_configs end end }, :storeconfigs_backend => { :type => :terminus, :default => "active_record", :desc => "Configure the backend terminus used for StoreConfigs. By default, this uses the ActiveRecord store, which directly talks to the database from within the Puppet Master process." } ) define_settings(:parser, :templatedir => { :default => "$vardir/templates", :type => :directory, :desc => "Where Puppet looks for template files. Can be a list of colon-separated directories. This setting is deprecated. Please put your templates in modules instead.", :deprecated => :completely, }, :allow_variables_with_dashes => { :default => false, :desc => <<-'EOT' Permit hyphens (`-`) in variable names and issue deprecation warnings about them. This setting **should always be `false`;** setting it to `true` will cause subtle and wide-ranging bugs. It will be removed in a future version. Hyphenated variables caused major problems in the language, but were allowed between Puppet 2.7.3 and 2.7.14. If you used them during this window, we apologize for the inconvenience --- you can temporarily set this to `true` in order to upgrade, and can rename your variables at your leisure. Please revert it to `false` after you have renamed all affected variables. EOT }, :parser => { :default => "current", :hook => proc do |value| envs = Puppet.lookup(:environments) { nil } envs.clear_all unless envs.nil? end, :desc => <<-'EOT' Selects the parser to use for parsing puppet manifests (in puppet DSL language/'.pp' files). Available choices are `current` (the default) and `future`. The `current` parser means that the released version of the parser should be used. The `future` parser is a "time travel to the future" allowing early exposure to new language features. What these features are will vary from release to release and they may be invididually configurable. Available Since Puppet 3.2. EOT }, :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' Makes the parser raise errors when referencing unknown variables. (This does not affect referencing variables that are explicitly set to undef). EOT } ) define_settings(:puppetdoc, :document_all => { :default => false, :type => :boolean, :desc => "Whether to document all resources when using `puppet doc` to generate manifest documentation.", } ) end puppet-3.8.5/lib/puppet/dsl.rb0000664005276200011600000000016312650174557016144 0ustar jenkinsjenkinsrequire 'puppet' module Puppet::DSL end require 'puppet/dsl/resource_type_api' require 'puppet/dsl/resource_api' puppet-3.8.5/lib/puppet/dsl/0000775005276200011600000000000012650174565015616 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/dsl/resource_api.rb0000664005276200011600000000551212650174557020627 0ustar jenkinsjenkins# This module adds functionality to a resource to make it # capable of evaluating the DSL resource type block and also # hooking into the scope system. require 'puppet/resource/type_collection_helper' class Puppet::DSL::ResourceAPI include Puppet::Resource::TypeCollectionHelper FUNCTION_MAP = {:acquire => :include} attr_reader :scope, :resource, :block def environment scope.environment end def evaluate set_instance_variables instance_eval(&block) end def initialize(resource, scope, block) @scope = scope @resource = resource @block = block end # Try to convert a missing method into a resource type or a function. def method_missing(name, *args) raise "MethodMissing loop when searching for #{name} with #{args.inspect}" if searching_for_method? @searching_for_method = true return create_resource(name, args[0], args[1]) if valid_type?(name) name = map_function(name) return call_function(name, args) if Puppet::Parser::Functions.function(name) super ensure @searching_for_method = false end def set_instance_variables resource.eachparam do |param| instance_variable_set("@#{param.name}", param.value) end @title = resource.title @name ||= resource.title end def create_resource(type, names, arguments = nil) names = [names] unless names.is_a?(Array) arguments ||= {} raise ArgumentError, "Resource arguments must be provided as a hash" unless arguments.is_a?(Hash) names.collect do |name| resource = Puppet::Parser::Resource.new(type, name, :scope => scope) arguments.each do |param, value| resource[param] = value end resource.exported = true if exporting? resource.virtual = true if virtualizing? scope.compiler.add_resource(scope, resource) resource end end def call_function(name, args) return false unless method = Puppet::Parser::Functions.function(name) scope.send(method, *args) end def export(resources = nil, &block) if resources resources.each { |resource| resource.exported = true } return resources end @exporting = true instance_eval(&block) ensure @exporting = false end def virtual(resources = nil, &block) if resources resources.each { |resource| resource.virtual = true } return resources end @virtualizing = true instance_eval(&block) ensure @virtualizing = false end def valid_type?(name) return true if [:class, :node].include?(name) return true if Puppet::Type.type(name) return(known_resource_types.definition(name) ? true : false) end private def exporting? @exporting end def map_function(name) FUNCTION_MAP[name] || name end def searching_for_method? @searching_for_method end def virtualizing? @virtualizing end end puppet-3.8.5/lib/puppet/dsl/resource_type_api.rb0000664005276200011600000000166112650174557021671 0ustar jenkinsjenkinsrequire 'puppet/resource/type' # Type of the objects inside of which pure ruby manifest files are # executed. Provides methods for creating defines, hostclasses, and # nodes. class Puppet::DSL::ResourceTypeAPI def initialize @__created_ast_objects__ = [] end def define(name, *args, &block) args = args.inject([]) do |result, item| if item.is_a?(Hash) item.each { |p, v| result << [p, v] } else result << item end result end @__created_ast_objects__.push Puppet::Parser::AST::Definition.new(name, {:arguments => args}, &block) nil end def hostclass(name, options = {}, &block) @__created_ast_objects__.push Puppet::Parser::AST::Hostclass.new(name, options, &block) nil end def node(name, options = {}, &block) name = [name] unless name.is_a?(Array) @__created_ast_objects__.push Puppet::Parser::AST::Node.new(name, options, &block) nil end end puppet-3.8.5/lib/puppet/environments.rb0000664005276200011600000003455312650174557020123 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 } root.instance_variable_set(:@future_parser, nil) unless root.nil? 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[:parser]) 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 # Old-style environments that come either from explicit stanzas in # puppet.conf or from dynamic environments created from use of `$environment` # in puppet.conf. # # @example Explicit Stanza # [environment_name] # modulepath=/var/my_env/modules # # @example Dynamic Environments # [master] # modulepath=/var/$environment/modules # # @api private class Legacy include EnvironmentCreator # @!macro loader_search_paths def search_paths ["file://#{Puppet[:config]}"] end # @note The list of environments for the Legacy environments is always # empty. # # @!macro loader_list def list [] end # @note Because the Legacy system cannot list out all of its environments, # get is able to return environments that are not returned by a call to # {#list}. # # @!macro loader_get def get(name) Puppet::Node::Environment.new(name) end # @note Because the Legacy system cannot list out all of its environments, # this method will never fail and is only calling get directly. # # @!macro loader_get_or_fail def get!(name) get(name) end # @note we could return something here, but since legacy environments # are deprecated, there is no point. # # @!macro loader_get_conf def get_conf(name) nil 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 = environment_dir @global_module_path = global_module_path 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, setting_values = nil) 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.watching = false 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 INFINITY = 1.0 / 0.0 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, # of 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) 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 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 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)) @cache.delete(name) @cache_expiration_service.evicted(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-3.8.5/lib/puppet/error.rb0000664005276200011600000000532012650174557016513 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(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?) if @file and @line and @pos "#{msg} at #{@file}:#{@line}:#{@pos}" elsif @file and @line "#{msg} at #{@file}:#{@line}" elsif @line and @pos "#{msg} at line #{@line}:#{@pos}" elsif @line "#{msg} at line #{@line}" elsif @file "#{msg} in #{@file}" else msg end 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 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 # def initialize(message, file=nil, line=nil, pos=nil, original=nil, issue_code= nil) super(message, file, line, pos, original) @issue_code = issue_code @basic_message = message end def to_s msg = super msg = "Could not parse for environment #{environment}: #{msg}" if environment msg = "#{msg} on node #{node}" if node msg 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 end puppet-3.8.5/lib/puppet/external/0000775005276200011600000000000012650174565016656 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/external/dot.rb0000664005276200011600000002224712650174557020001 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 glogal 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 @ports 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 @nodes @dot_string 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-3.8.5/lib/puppet/external/nagios.rb0000664005276200011600000000151012650174557020461 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-3.8.5/lib/puppet/external/nagios/0000775005276200011600000000000012650174565020136 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/external/nagios/base.rb0000664005276200011600000003005312650174557021377 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 { |oc| str += "objectclass: #{oc}\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-3.8.5/lib/puppet/external/nagios/grammar.ry0000664005276200011600000001373612650174557022153 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 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-3.8.5/lib/puppet/external/nagios/makefile0000664005276200011600000000024712650174557021642 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-3.8.5/lib/puppet/external/nagios/parser.rb0000664005276200011600000002172412650174557021766 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 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-3.8.5/lib/puppet/external/pson/0000775005276200011600000000000012650174565017635 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/external/pson/common.rb0000664005276200011600000003176612650174557021470 0ustar jenkinsjenkinsrequire 'puppet/external/pson/version' 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 def registered_document_types @registered_document_types ||= {} end # Register a class-constant for deserializaion. def register_document_type(name,klass) registered_document_types[name.to_s] = klass 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 registered_document_types[path] || 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}" 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. # * *create_additions*: If set to false, the Parser doesn't create # additions even if a matching class and create_id was found. This option # defaults to true. 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. # * *create_additions*: If set to false, the Parser doesn't create # additions even if a matching class and create_id was found. 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 # Ouputs _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-3.8.5/lib/puppet/external/pson/pure.rb0000664005276200011600000000061112650174557021134 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-3.8.5/lib/puppet/external/pson/pure/0000775005276200011600000000000012650174565020610 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/external/pson/pure/generator.rb0000664005276200011600000003172712650174557023136 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 extinding 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 { 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-3.8.5/lib/puppet/external/pson/pure/parser.rb0000664005276200011600000002501312650174557022433 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, horicontal 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. # * *create_additions*: If set to false, the Parser doesn't create # additions even if a matching class and create_id was found. This option # defaults to true. # * *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] ca = true ca = opts[:create_additions] if opts.key?(:create_additions) @create_id = ca ? PSON.create_id : nil @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 if @create_id and klassname = result[@create_id] klass = PSON.deep_const_get klassname break unless klass and klass.pson_creatable? result = klass.pson_create(result) end break when skip(IGNORE) ; else raise ParserError, "unexpected token in object at '#{peek(20)}'!" end end result end end end end puppet-3.8.5/lib/puppet/external/pson/version.rb0000664005276200011600000000041712650174557021652 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-3.8.5/lib/puppet/face.rb0000664005276200011600000000112412650174557016256 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-3.8.5/lib/puppet/face/0000775005276200011600000000000012650174565015732 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/face/ca.rb0000664005276200011600000001747312650174557016657 0ustar jenkinsjenkinsrequire 'puppet/face' Puppet::Face.define(:ca, '0.1.0') do copyright "Puppet Labs", 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| 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 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" else raise end rescue ArgumentError => e if e.to_s =~ /A Certificate already exists for / "#{host} already has a certificate" 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 ca.sign(host, options[:allow_dns_alt_names]) 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 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| raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca? unless Puppet::SSL::CertificateAuthority.instance 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| 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.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 #{value[:host]}: #{value[:error]}" end end end end puppet-3.8.5/lib/puppet/face/catalog.rb0000664005276200011600000001106512650174557017675 0ustar jenkinsjenkinsrequire 'puppet/indirector/face' Puppet::Indirector::Face.define(:catalog, '0.0.1') do copyright "Puppet Labs", 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 get_action(:destroy).summary "Invalid for this subcommand." get_action(:search).summary "Invalid for this subcommand." 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 From `secret_agent.rb` (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("apply") report.configuration_version = catalog.version report.environment = Puppet[:environment] Puppet::Util::Log.newdestination(report) begin benchmark(:notice, "Finished catalog run") 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 From `secret_agent.rb` (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 yaml" nil end end end puppet-3.8.5/lib/puppet/face/catalog/0000775005276200011600000000000012650174565017344 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/face/catalog/select.rb0000664005276200011600000000333312650174557021153 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-3.8.5/lib/puppet/face/certificate.rb0000664005276200011600000001333012650174557020542 0ustar jenkinsjenkinsrequire 'puppet/indirector/face' require 'puppet/ssl/host' Puppet::Indirector::Face.define(:certificate, '0.0.1') do copyright "Puppet Labs", 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 "" 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. 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 "" 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 ca.sign(name, options[:allow_dns_alt_names]) end end end # Indirector action doc overrides find = get_action(:find) find.summary "Retrieve a certificate." find.arguments "" 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 "" destroy.returns "Nothing." destroy.description <<-EOT Deletes a certificate. This action currently only works on the local CA. EOT get_action(:search).summary "Invalid for this subcommand." get_action(:save).summary "Invalid for this subcommand." get_action(:save).description "Invalid for this subcommand." end puppet-3.8.5/lib/puppet/face/certificate_request.rb0000664005276200011600000000334312650174557022315 0ustar jenkinsjenkinsrequire 'puppet/indirector/face' Puppet::Indirector::Face.define(:certificate_request, '0.0.1') do copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" summary "Manage certificate requests." description <<-EOT This subcommand retrieves and submits certificate signing requests (CSRs). EOT # Per-action doc overrides get_action(:destroy).summary "Invalid for this subcommand." find = get_action(:find) find.summary "Retrieve a single CSR." find.arguments "" find.render_as = :s find.returns <<-EOT A single certificate request. When used from the Ruby API, returns a Puppet::SSL::CertificateRequest object. 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 "" 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 "" end puppet-3.8.5/lib/puppet/face/certificate_revocation_list.rb0000664005276200011600000000416112650174557024030 0ustar jenkinsjenkinsrequire 'puppet/indirector/face' Puppet::Indirector::Face.define(:certificate_revocation_list, '0.0.1') do copyright "Puppet Labs", 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.arguments "" 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. Due to a known bug, this action requires a dummy argument, the content of which is irrelevant. EOT find.notes <<-EOT Although this action always returns the CRL from the specified terminus, it requires a dummy argument; this is a known bug. EOT find.examples <<-EXAMPLES Retrieve a copy of the puppet master's CRL: $ puppet certificate_revocation_list find crl --terminus rest EXAMPLES destroy = get_action(:destroy) destroy.summary "Delete the certificate revocation list." destroy.arguments "" 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 get_action(:search).summary "Invalid for this subcommand." get_action(:save).summary "Invalid for this subcommand." get_action(:save).description "Invalid for this subcommand." end puppet-3.8.5/lib/puppet/face/config.rb0000664005276200011600000001014312650174557017524 0ustar jenkinsjenkinsrequire 'puppet/face' require 'puppet/settings/ini_file' Puppet::Face.define(:config, '0.0.1') do copyright "Puppet Labs", 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 http://docs.puppetlabs.com/references/latest/configuration.html." option "--section SECTION_NAME" do default_to { "main" } 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 | [ ...]" description <<-'EOT' Prints the value of a single setting or a list of settings. This action is an alternate interface to the information available with `puppet --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 args = Puppet.settings.to_a.collect(&:first) if args.empty? || args == ['all'] 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), } 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 args.length == 1 puts values.interpolate(args[0].to_sym) else args.each do |setting_name| puts "#{setting_name} = #{values.interpolate(setting_name.to_sym)}" end end end nil end 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/puppet Set the vardir for only the agent: $ puppet config set vardir /var/lib/puppetagent --section agent EOT when_invoked do |name, value, options| path = Puppet::FileSystem.pathname(Puppet.settings.which_configuration_file) Puppet::FileSystem.touch(path) Puppet::FileSystem.open(path, nil, 'r+') do |file| Puppet::Settings::IniFile.update(file) do |config| config.set(options[:section], name, value) end end nil end end end puppet-3.8.5/lib/puppet/face/facts.rb0000664005276200011600000000543412650174557017366 0ustar jenkinsjenkinsrequire 'puppet/indirector/face' require 'puppet/node/facts' Puppet::Indirector::Face.define(:facts, '0.0.1') do copyright "Puppet Labs", 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), look up facts reported by other systems, and submit facts to the puppet master. When used with the `rest` terminus, this subcommand is essentially a front-end to the inventory service REST API. See the inventory service documentation at for more detail. 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 x Ask the puppet master for facts for an arbitrary node: $ puppet facts find somenode.puppetlabs.lan --terminus rest Query a DB-backed inventory directly (bypassing the REST API): $ puppet facts find somenode.puppetlabs.lan --terminus inventory_active_record --run_mode master EOT get_action(:destroy).summary "Invalid for this subcommand." get_action(:search).summary "Invalid for this subcommand." 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 save access to the `facts` REST terminus. Puppet agent does not use this facility, and it is turned off by default. See for more details. EOT examples <<-'EOT' Upload facts: $ puppet facts upload EOT render_as :yaml when_invoked do |options| Puppet::Node::Facts.indirection.terminus_class = :facter facts = Puppet::Node::Facts.indirection.find(Puppet[:certname]) Puppet::Node::Facts.indirection.terminus_class = :rest Puppet::Node::Facts.indirection.save(facts) Puppet.notice "Uploaded facts for '#{Puppet[:certname]}'" nil end end end puppet-3.8.5/lib/puppet/face/file.rb0000664005276200011600000000341112650174557017176 0ustar jenkinsjenkinsrequire 'puppet/indirector/face' Puppet::Indirector::Face.define(:file, '0.0.1') do copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" summary "Retrieve and store files in a filebucket" description <<-'EOT' This subcommand interacts with objects stored in a local or remote filebucket. File objects are accessed by their MD5 sum; see the examples for the relevant syntax. EOT notes <<-'EOT' To retrieve the unmunged contents of a file, you must call find with --render-as s. Rendering as yaml will return a hash of metadata about the file, including its contents. This subcommand does not interact with the `clientbucketdir` (the default local filebucket for puppet agent); it interacts with the primary "master"-type filebucket located in the `bucketdir`. If you wish to interact with puppet agent's default filebucket, you'll need to set the <--bucketdir> option appropriately when invoking actions. EOT file = get_action(:find) file.summary "Retrieve a file from the filebucket." file.arguments "md5/" file.returns <<-'EOT' The file object with the specified checksum. RENDERING ISSUES: Rendering as a string returns the contents of the file object; rendering as yaml returns a hash of metadata about said file, including but not limited to its contents. Rendering as json is currently broken, and returns a hash containing only the contents of the file. EOT file.examples <<-'EOT' Retrieve the contents of a file: $ puppet file find md5/9aedba7f413c97dc65895b1cd9421f2c --render-as s EOT get_action(:search).summary "Invalid for this subcommand." get_action(:destroy).summary "Invalid for this subcommand." set_indirection_name :file_bucket_file end puppet-3.8.5/lib/puppet/face/file/0000775005276200011600000000000012650174565016651 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/face/file/download.rb0000664005276200011600000000366312650174557021016 0ustar jenkinsjenkins# Download a specified file into the local filebucket. Puppet::Face.define(:file, '0.0.1') do action :download do |*args| summary "Download a file into the local filebucket." arguments "( {md5} | )" returns "Nothing." description <<-EOT Downloads a file from the puppet master's filebucket and duplicates it in the local filebucket. This action's checksum syntax differs from `find`'s, and it can accept a URL. EOT examples <<-'EOT' Download a file by URL: $ puppet file download puppet:///modules/editors/vim/.vimrc Download a file by MD5 sum: $ puppet file download {md5}8f798d4e754db0ac89186bbaeaf0af18 EOT when_invoked do |sum, options| if sum =~ /^puppet:\/\// # it's a puppet url require 'puppet/file_serving' require 'puppet/file_serving/content' unless content = Puppet::FileServing::Content.indirection.find(sum) raise "Could not find metadata for #{sum}" end pathname = Puppet::FileSystem.pathname(content.full_path()) file = Puppet::FileBucket::File.new(pathname) else tester = Object.new tester.extend(Puppet::Util::Checksums) type = tester.sumtype(sum) sumdata = tester.sumdata(sum) key = "#{type}/#{sumdata}" Puppet::FileBucket::File.indirection.terminus_class = :file if Puppet::FileBucket::File.indirection.head(key) Puppet.info "Content for '#{sum}' already exists" return end Puppet::FileBucket::File.indirection.terminus_class = :rest raise "Could not download content for '#{sum}'" unless file = Puppet::FileBucket::File.indirection.find(key) end Puppet::FileBucket::File.indirection.terminus_class = :file Puppet.notice "Saved #{sum} to filebucket" Puppet::FileBucket::File.indirection.save file return nil end end end puppet-3.8.5/lib/puppet/face/file/store.rb0000664005276200011600000000107112650174557020332 0ustar jenkinsjenkins# Store a specified file in our filebucket. Puppet::Face.define(:file, '0.0.1') do action :store do |*args| summary "Store a file in the local filebucket." arguments "" returns "Nothing." examples <<-EOT Store a file: $ puppet file store /root/.bashrc EOT when_invoked do |path, options| file = Puppet::FileBucket::File.new(Puppet::FileSystem.pathname(path)) Puppet::FileBucket::File.indirection.terminus_class = :file Puppet::FileBucket::File.indirection.save file file.checksum end end end puppet-3.8.5/lib/puppet/face/help.rb0000664005276200011600000001541312650174557017214 0ustar jenkinsjenkinsrequire '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 Labs", 2011 license "Apache 2 license; see COPYING" summary "Display Puppet help." action(:help) do summary "Display help about Puppet subcommands and their actions." arguments "[] []" 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| # Check our invocation, because we want varargs and can't do defaults # yet. REVISIT: when we do option defaults, and positional options, we # should rewrite this to use those. --daniel 2011-04-04 options = args.pop if options.nil? or args.length > 2 then if args.select { |x| x == 'help' }.length > 2 then c = "\n %'(),-./=ADEFHILORSTUXY\\_`gnv|".split('') i = <<-'EOT'.gsub(/\s*/, '').to_i(36) 3he6737w1aghshs6nwrivl8mz5mu9nywg9tbtlt081uv6fq5kvxse1td3tj1wvccmte806nb cy6de2ogw0fqjymbfwi6a304vd56vlq71atwmqsvz3gpu0hj42200otlycweufh0hylu79t3 gmrijm6pgn26ic575qkexyuoncbujv0vcscgzh5us2swklsp5cqnuanlrbnget7rt3956kam j8adhdrzqqt9bor0cv2fqgkloref0ygk3dekiwfj1zxrt13moyhn217yy6w4shwyywik7w0l xtuevmh0m7xp6eoswin70khm5nrggkui6z8vdjnrgdqeojq40fya5qexk97g4d8qgw0hvokr pli1biaz503grqf2ycy0ppkhz1hwhl6ifbpet7xd6jjepq4oe0ofl575lxdzjeg25217zyl4 nokn6tj5pq7gcdsjre75rqylydh7iia7s3yrko4f5ud9v8hdtqhu60stcitirvfj6zphppmx 7wfm7i9641d00bhs44n6vh6qvx39pg3urifgr6ihx3e0j1ychzypunyou7iplevitkyg6gbg wm08oy1rvogcjakkqc1f7y1awdfvlb4ego8wrtgu9vzw4vmj59utwifn2ejcs569dh1oaavi sc581n7jjg1dugzdu094fdobtx6rsvk3sfctvqnr36xctold EOT 353.times{i,x=i.divmod(1184);a,b=x.divmod(37);print(c[a]*b)} end raise ArgumentError, "Puppet help only takes two (optional) arguments: a subcommand and an action" end version = :current if options.has_key? :version then if options[:version].to_s !~ /^current$/i then version = options[:version] else if args.length == 0 then raise ArgumentError, "Version only makes sense when a Faces subcommand is given" end end end return erb('global.erb').result(binding) if args.empty? facename, actionname = args if legacy_applications.include? facename then if actionname then raise ArgumentError, "Legacy subcommands don't take actions" end return render_application_help(facename) else return render_face_help(facename, actionname, version) end end end def render_application_help(applicationname) return Puppet::Application[applicationname].help end def render_face_help(facename, actionname, version) face, action = load_face_help(facename, actionname, version) return template_for(face, action).result(binding) end def load_face_help(facename, actionname, version) begin face = Puppet::Face[facename.to_sym, version] rescue Puppet::Error => detail msg = <<-MSG Could not load help for the face #{facename}. Please check the error logs for more information. Detail: "#{detail.message}" MSG fail ArgumentError, msg, detail.backtrace end if actionname action = face.get_action(actionname.to_sym) if not action fail ArgumentError, "Unable to load action #{actionname} from #{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] result << [appname, face.summary] rescue Puppet::Error result << [ "! #{appname}", "! Subcommand unavailable due to error. Check error logs." ] end else result << [appname, horribly_extract_summary_from(appname)] end end end def horribly_extract_summary_from(appname) begin 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) then return md[1] end end rescue Exception # Damn, but I hate this: we just ignore errors here, no matter what # class they are. Meh. 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-3.8.5/lib/puppet/face/help/0000775005276200011600000000000012650174565016662 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/face/help/action.erb0000664005276200011600000000466712650174557020647 0ustar jenkinsjenkins<%# encoding: UTF-8%> <% 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 man <%= face.name %>' or 'man puppet-<%= face.name %>' for full help. puppet-3.8.5/lib/puppet/face/help/face.erb0000664005276200011600000000617312650174557020262 0ustar jenkinsjenkins<%# encoding: UTF-8%> <% 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 man <%= face.name %>' or 'man puppet-<%= face.name %>' for full help. puppet-3.8.5/lib/puppet/face/help/global.erb0000664005276200011600000000150212650174557020613 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-3.8.5/lib/puppet/face/help/man.erb0000664005276200011600000001037012650174557020131 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-3.8.5/lib/puppet/face/instrumentation_data.rb0000664005276200011600000000164312650174557022520 0ustar jenkinsjenkinsrequire 'puppet/indirector/face' require 'puppet/util/instrumentation/data' Puppet::Indirector::Face.define(:instrumentation_data, '0.0.1') do copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" summary "Manage instrumentation listener accumulated data. DEPRECATED." description <<-EOT This subcommand allows to retrieve the various listener data. (DEPRECATED) This subcommand will be removed in Puppet 4.0. EOT get_action(:destroy).summary "Invalid for this subcommand." get_action(:save).summary "Invalid for this subcommand." get_action(:search).summary "Invalid for this subcommand." find = get_action(:find) find.summary "Retrieve listener data." find.render_as = :pson find.returns <<-EOT The data of an instrumentation listener EOT find.examples <<-EOT Retrieve listener data: $ puppet instrumentation_data find performance --terminus rest EOT end puppet-3.8.5/lib/puppet/face/instrumentation_listener.rb0000664005276200011600000000577112650174557023442 0ustar jenkinsjenkinsrequire 'puppet/indirector/face' require 'puppet/util/instrumentation/listener' Puppet::Indirector::Face.define(:instrumentation_listener, '0.0.1') do copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" summary "Manage instrumentation listeners. DEPRECATED." description <<-EOT This subcommand enables/disables or list instrumentation listeners. (DEPRECATED) This subcommand will be removed in Puppet 4.0. EOT get_action(:destroy).summary "Invalid for this subcommand." find = get_action(:find) find.summary "Retrieve a single listener." find.render_as = :pson find.returns <<-EOT The status of an instrumentation listener EOT find.examples <<-EOT Retrieve a given listener: $ puppet instrumentation_listener find performance --terminus rest EOT search = get_action(:search) search.summary "Retrieve all instrumentation listeners statuses." search.arguments "" search.render_as = :pson search.returns <<-EOT The statuses of all instrumentation listeners EOT search.short_description <<-EOT This retrieves all instrumentation listeners EOT search.notes <<-EOT Although this action always returns all instrumentation listeners, it requires a dummy search key; this is a known bug. EOT search.examples <<-EOT Retrieve the state of the listeners running in the remote puppet master: $ puppet instrumentation_listener search x --terminus rest EOT def manage(name, activate) Puppet::Util::Instrumentation::Listener.indirection.terminus_class = :rest listener = Puppet::Face[:instrumentation_listener, '0.0.1'].find(name) if listener listener.enabled = activate Puppet::Face[:instrumentation_listener, '0.0.1'].save(listener) end end action :enable do summary "Enable a given instrumentation listener." arguments "" returns "Nothing." description <<-EOT Enable a given instrumentation listener. After being enabled the listener will start receiving instrumentation notifications from the probes if those are enabled. EOT examples <<-EOT Enable the "performance" listener in the running master: $ puppet instrumentation_listener enable performance --terminus rest EOT when_invoked do |name, options| manage(name, true) end end action :disable do summary "Disable a given instrumentation listener." arguments "" returns "Nothing." description <<-EOT Disable a given instrumentation listener. After being disabled the listener will stop receiving instrumentation notifications from the probes. EOT examples <<-EOT Disable the "performance" listener in the running master: $ puppet instrumentation_listener disable performance --terminus rest EOT when_invoked do |name, options| manage(name, false) end end get_action(:save).summary "API only: modify an instrumentation listener status." get_action(:save).arguments "" end puppet-3.8.5/lib/puppet/face/instrumentation_probe.rb0000664005276200011600000000464612650174557022724 0ustar jenkinsjenkinsrequire 'puppet/indirector/face' require 'puppet/util/instrumentation/indirection_probe' Puppet::Indirector::Face.define(:instrumentation_probe, '0.0.1') do copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" summary "Manage instrumentation probes. Deprecated" description <<-EOT This subcommand enables/disables or list instrumentation listeners. (DEPRECATED) This subcommand will be removed in Puppet 4.0. EOT get_action(:find).summary "Invalid for this subcommand." search = get_action(:search) search.summary "Retrieve all probe statuses." search.arguments "" search.render_as = :pson search.returns <<-EOT The statuses of all instrumentation probes EOT search.short_description <<-EOT This retrieves all instrumentation probes EOT search.notes <<-EOT Although this action always returns all instrumentation probes, it requires a dummy search key; this is a known bug. EOT search.examples <<-EOT Retrieve the state of the probes running in the remote puppet master: $ puppet instrumentation_probe search x --terminus rest EOT action :enable do summary "Enable all instrumentation probes." arguments "" returns "Nothing." description <<-EOT Enable all instrumentation probes. After being enabled, all enabled listeners will start receiving instrumentation notifications from the probes. EOT examples <<-EOT Enable the probes for the running master: $ puppet instrumentation_probe enable x --terminus rest EOT when_invoked do |name, options| Puppet::Face[:instrumentation_probe, '0.0.1'].save(nil) end end action :disable do summary "Disable all instrumentation probes." arguments "" returns "Nothing." description <<-EOT Disable all instrumentation probes. After being disabled, no listeners will receive instrumentation notifications. EOT examples <<-EOT Disable the probes for the running master: $ puppet instrumentation_probe disable x --terminus rest EOT when_invoked do |name, options| Puppet::Face[:instrumentation_probe, '0.0.1'].destroy(nil) end end get_action(:save).summary "API only: enable all instrumentation probes." get_action(:save).arguments "" get_action(:destroy).summary "API only: disable all instrumentation probes." get_action(:destroy).arguments "" end puppet-3.8.5/lib/puppet/face/key.rb0000664005276200011600000000076712650174557017062 0ustar jenkinsjenkinsrequire 'puppet/indirector/face' Puppet::Indirector::Face.define(:key, '0.0.1') do copyright "Puppet Labs", 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 end puppet-3.8.5/lib/puppet/face/man.rb0000664005276200011600000000645312650174557017043 0ustar jenkinsjenkinsrequire 'puppet/face' require 'puppet/util' require 'pathname' require 'erb' Puppet::Face.define(:man, '0.0.1') do copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" summary "Display Puppet manual pages." description <<-EOT This subcommand displays manual pages for all Puppet subcommands. If the `ronn` gem () is installed on your system, puppet man will display fully-formated 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 "" 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 manual page for a subcommand: $ puppet man facts EOT default when_invoked do |name, options| if legacy_applications.include? name then return Puppet::Application[name].help end face = Puppet::Face[name.to_sym, :current] 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 then # ronn is a stupid about pager selection, we can be smarter. :) if pager then ENV['PAGER'] = pager end args = "--man --manual='Puppet Manual' --organization='Puppet Labs, LLC'" IO.popen("#{ronn} #{args}", 'w') do |fh| fh.write text end '' # suppress local output, neh? elsif pager then IO.popen(pager, 'w') do |fh| fh.write text end '' else text end end 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 end puppet-3.8.5/lib/puppet/face/module.rb0000664005276200011600000000116212650174557017545 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 Labs", 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-3.8.5/lib/puppet/face/module/0000775005276200011600000000000012650174565017217 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/face/module/build.rb0000664005276200011600000000447412650174557020655 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. This action uses the Modulefile in the module directory to set metadata used by the Forge. See for more about writing modulefiles. 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 or Modulefile in module root #{pwd} or parent directories. See for required file format." end else unless Puppet::ModuleTool.is_module_root?(module_path) raise "Unable to find metadata.json or Modulefile in module root #{module_path}. See for required file format." end end 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: " + return_value.expand_path.to_s end end end puppet-3.8.5/lib/puppet/face/module/changes.rb0000664005276200011600000000240012650174557021151 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 metadata. EOT returns "Array of strings representing paths of modified files." examples <<-EOT Show modified files of an installed module: $ puppet module changes /etc/puppet/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.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 "#{return_value.size} files modified" end return_value.map do |changed_file| "#{changed_file}" end.join("\n") end end end puppet-3.8.5/lib/puppet/face/module/generate.rb0000664005276200011600000001765612650174557021356 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 # Modulefile. 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.inspect}, you must specify a dash-separated username and module name." 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}." result.join("\n") end end end module Puppet::ModuleTool::Generate module_function def generate(metadata, skip_interview = false) 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" puts "following questions; if the question is not applicable to this module, feel free" puts "to leave it blank." begin puts puts "Puppet uses Semantic Versioning (semver.org) to version modules." puts "What version is this module? [#{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? [#{metadata.author}]" metadata.update 'author' => user_input(metadata.author) puts puts "What license does this module code fall under? [#{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?#{ 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?#{ 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.dashed_name raise ArgumentError, "#{@dest} already exists." if @dest.exist? return @dest end def duplicate_skeleton(metadata) dest = destination(metadata) puts Puppet.notice "Generating module at #{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') { |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-3.8.5/lib/puppet/face/module/install.rb0000664005276200011600000001242512650174557021217 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. 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/puppet/modules ... Downloading from http://forgeapi.puppetlabs.com ... Installing -- do not interrupt ... /etc/puppet/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/puppet/environments/development/modules ... Downloading from http://forgeapi.puppetlabs.com ... Installing -- do not interrupt ... /etc/puppet/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/puppet/modules ... Downloading from http://forgeapi.puppetlabs.com ... Installing -- do not interrupt ... /etc/puppet/modules └── puppetlabs-vcsrepo (v0.0.4) Install a module into a specific directory: $ puppet module install puppetlabs-vcsrepo --target-dir=/usr/share/puppet/modules Preparing to install into /usr/share/puppet/modules ... Downloading from http://forgeapi.puppetlabs.com ... Installing -- do not interrupt ... /usr/share/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=/usr/share/puppet/modules --modulepath /etc/puppet/modules Preparing to install into /usr/share/puppet/modules ... Downloading from http://forgeapi.puppetlabs.com ... Installing -- do not interrupt ... /usr/share/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/puppet/modules ... Downloading from http://forgeapi.puppetlabs.com ... Installing -- do not interrupt ... /etc/puppet/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/puppet/modules ... Installing -- do not interrupt ... /etc/puppet/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 when_invoked do |name, options| Puppet::ModuleTool.set_option_defaults options Puppet.notice "Preparing to install into #{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} #{return_value[:version]} is already installed." 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-3.8.5/lib/puppet/face/module/list.rb0000664005276200011600000002213012650174557020516 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 examples <<-'EOT' List installed modules: $ puppet module list /etc/puppet/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) /usr/share/puppet/modules (no modules installed) List installed modules in a tree view: $ puppet module list --tree /etc/puppet/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) /usr/share/puppet/modules (no modules installed) List installed modules from a specified environment: $ puppet module list --environment production /etc/puppet/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) /usr/share/puppet/modules (no modules installed) List installed modules from a specified modulepath: $ puppet module list --modulepath /usr/share/puppet/modules /usr/share/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 = '' 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 => { :title => "Non semantic version dependency" }, :missing => { :title => "Missing dependency" }, :version_mismatch => { :title => "Module '%s' (v%s) fails to meet some dependencies:" } } @unmet_deps = {} error_types.each_key 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})" msg << " requires '#{dep_name}' (#{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_by {|dep| dep }.each do |dep| name = dep.gsub('/', '-') title = error_types[type][:title] errors = @unmet_deps[type][dep][:errors] version = @unmet_deps[type][dep][:version] msg = case type when :version_mismatch title % [name, version] + "\n" when :non_semantic_version title + " '#{name}' (v#{version}):\n" else title + " '#{name}':\n" 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/puppet/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-3.8.5/lib/puppet/face/module/search.rb0000664005276200011600000000677412650174557021030 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).run end when_rendering :console do |results, term, options| if results[:result] == :failure raise results[:error][:multiline] end return "No results found for '#{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| { 'full_name' => [ hash['full_name'], result['full_name'].length ].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 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] } 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-3.8.5/lib/puppet/face/module/uninstall.rb0000664005276200011600000000514212650174557021560 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). EOT returns "Hash of module objects representing uninstalled modules and related errors." examples <<-'EOT' Uninstall a module: $ puppet module uninstall puppetlabs-ssh Removed /etc/puppet/modules/ssh (v1.0.0) Uninstall a module from a specific directory: $ puppet module uninstall puppetlabs-ssh --modulepath /usr/share/puppet/modules Removed /usr/share/puppet/modules/ssh (v1.0.0) Uninstall a module from a specific environment: $ puppet module uninstall puppetlabs-ssh --environment development Removed /etc/puppet/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/puppet/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 when_invoked do |name, options| name = name.gsub('/', '-') Puppet::ModuleTool.set_option_defaults options Puppet.notice "Preparing to uninstall '#{name}'" << (options[:version] ? " (#{colorize(:cyan, options[:version].sub(/^(?=\d)/, 'v'))})" : '') << " ..." 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 "Removed '#{return_value[:module_name]}'" << (mod.version ? " (#{colorize(:cyan, mod.version.to_s.sub(/^(?=\d)/, 'v'))})" : '') << " from #{mod.modulepath}" end end end end puppet-3.8.5/lib/puppet/face/module/upgrade.rb0000664005276200011600000000511112650174557021172 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. EOT returns "Hash" examples <<-EOT upgrade an installed module to the latest version $ puppet module upgrade puppetlabs-apache /etc/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/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 /usr/share/puppet/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 when_invoked do |name, options| name = name.gsub('/', '-') Puppet.notice "Preparing to upgrade '#{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-3.8.5/lib/puppet/face/node.rb0000664005276200011600000000324712650174557017213 0ustar jenkinsjenkinsrequire 'puppet/indirector/face' Puppet::Indirector::Face.define(:node, '0.0.1') do copyright "Puppet Labs", 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 get_action(:destroy).summary "Invalid for this subcommand." get_action(:search).summary "Invalid for this subcommand." get_action(:save).summary "Invalid for this subcommand." get_action(:save).description "Invalid for this subcommand." find = get_action(:find) find.summary "Retrieve a node object." find.arguments "" 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-3.8.5/lib/puppet/face/node/0000775005276200011600000000000012650174565016657 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/face/node/clean.rb0000664005276200011600000001244412650174557020274 0ustar jenkinsjenkinsPuppet::Face.define(:node, '0.0.1') do action(:clean) do option "--[no-]unexport" do summary "Whether to remove this node's exported resources from other nodes" end summary "Clean up everything a puppetmaster knows about a node." arguments " [ ...]" description <<-'EOT' Clean up everything a puppet master knows about a node, including certificates and storeconfigs data. The full list of info cleaned by this action is: - ($vardir/ssl/ca/signed/node.domain.pem) - ($vardir/yaml/facts/node.domain.yaml) - ($vardir/yaml/node/node.domain.yaml) - ($vardir/reports/node.domain) - (in database) The clean action can either remove all data from a host in your storeconfigs database, or, with the <--unexport> option, turn every exported resource supporting ensure to absent so that any other host that collected those resources can remove them. Without unexporting, a removed node's exported resources become unmanaged by Puppet, and may linger as cruft unless you are purging that resource type. 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, options[:unexport]) } end end def cleanup(node, unexport) clean_cert(node) clean_cached_facts(node) clean_cached_node(node) clean_reports(node) clean_storeconfigs(node, unexport) 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" else Puppet.info "Not managing #{node} certs as this host is not a CA" end end # clean facts for +host+ def clean_cached_facts(node) Puppet::Node::Facts.indirection.destroy(node) Puppet.info "#{node}'s facts removed" end # clean cached node +host+ def clean_cached_node(node) Puppet::Node.indirection.destroy(node) Puppet.info "#{node}'s cached node removed" end # clean node reports for +host+ def clean_reports(node) Puppet::Transaction::Report.indirection.destroy(node) Puppet.info "#{node}'s reports removed" end # clean storeconfig for +node+ def clean_storeconfigs(node, do_unexport=false) return unless Puppet[:storeconfigs] && Puppet.features.rails? require 'puppet/rails' Puppet::Rails.connect unless rails_node = Puppet::Rails::Host.find_by_name(node) Puppet.notice "No entries found for #{node} in storedconfigs." return end if do_unexport unexport(rails_node) Puppet.notice "Force #{node}'s exported resources to absent" Puppet.warning "Please wait until all other hosts have checked out their configuration before finishing the cleanup with:" Puppet.warning "$ puppet node clean #{node}" else rails_node.destroy Puppet.notice "#{node} storeconfigs removed" end end def unexport(node) # fetch all exported resource query = {:include => {:param_values => :param_name}} query[:conditions] = [ "exported=? AND host_id=?", true, node.id ] Puppet::Rails::Resource.find(:all, query).each do |resource| if type_is_ensurable(resource) line = 0 param_name = Puppet::Rails::ParamName.find_or_create_by_name("ensure") if ensure_param = resource.param_values.find( :first, :conditions => [ 'param_name_id = ?', param_name.id ] ) line = ensure_param.line.to_i Puppet::Rails::ParamValue.delete(ensure_param.id); end # force ensure parameter to "absent" resource.param_values.create( :value => "absent", :line => line, :param_name => param_name ) Puppet.info("#{resource.name} has been marked as \"absent\"") end end 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-3.8.5/lib/puppet/face/parser.rb0000664005276200011600000001202612650174557017555 0ustar jenkinsjenkinsrequire 'puppet/face' require 'puppet/parser' Puppet::Face.define(:parser, '0.0.1') do copyright "Puppet Labs", 2011 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 with --parser current, the validation stops after the first encountered issue. When validating with --parser future, 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/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}" 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#{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 "-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. It automatically turns on the future parser for the parsing. 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 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 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 missing_files = [] 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(File.read(file), 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 = "" dumper = Puppet::Pops::Model::ModelTreeDumper.new 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 output << dumper.dump(parse_result) << "\n" 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) 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 puppet-3.8.5/lib/puppet/face/plugin.rb0000664005276200011600000000471012650174557017560 0ustar jenkinsjenkinsrequire 'puppet/face' Puppet::Face.define(:plugin, '0.0.1') do copyright "Puppet Labs", 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| require 'puppet/configurer/downloader' remote_environment_for_plugins = Puppet::Node::Environment.remote(Puppet[:environment]) result = Puppet::Configurer::Downloader.new("plugin", Puppet[:plugindest], Puppet[:pluginsource], Puppet[:pluginsignore], remote_environment_for_plugins).evaluate if Puppet.features.external_facts? result += Puppet::Configurer::Downloader.new("pluginfacts", Puppet[:pluginfactdest], Puppet[:pluginfactsource], Puppet[:pluginsignore], remote_environment_for_plugins).evaluate end result end when_rendering :console do |value| if value.empty? then "No plugins downloaded." else "Downloaded these plugins: #{value.join(', ')}" end end end end puppet-3.8.5/lib/puppet/face/report.rb0000664005276200011600000000351212650174557017574 0ustar jenkinsjenkinsrequire 'puppet/indirector/face' Puppet::Indirector::Face.define(:report, '0.0.1') do copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" summary "Create, display, and submit reports." get_action(:find).summary "Invalid for this face." get_action(:search).summary "Invalid for this face." get_action(:destroy).summary "Invalid for this face." 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' From secret_agent.rb (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 #{report.name}" rescue => detail Puppet.log_exception(detail, "Could not send report: #{detail}") end end end end puppet-3.8.5/lib/puppet/face/resource.rb0000664005276200011600000000321512650174557020110 0ustar jenkinsjenkinsrequire 'puppet/indirector/face' Puppet::Indirector::Face.define(:resource, '0.0.1') do copyright "Puppet Labs", 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 get_action(:destroy).summary "Invalid for this subcommand." 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_pson 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-3.8.5/lib/puppet/face/resource_type.rb�������������������������������������������������������0000664�0052762�0001160�00000005306�12650174557�021154� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/face' Puppet::Indirector::Face.define(:resource_type, '0.0.1') do copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" summary "View classes, defined resource types, and nodes from all manifests." description <<-'EOT' This subcommand reads information about the resource collections (classes, nodes, and defined types) available in Puppet's site manifest and modules. It will eventually be extended to examine native resource types. EOT notes <<-'EOT' The `find` and `search` actions return similar hashes of resource collection info. These hashes will include the following four keys: * `file` (a string) * `name` (a string) * `type` (<hostclass>, <definition>, or <node>) * `line` (an integer) They may optionally include the following keys: * `parent` (<name_of_resource_collection>) * `arguments` (a hash of parameters and default values) * `doc` (a string) EOT # Action documentation overrides: get_action(:save).summary = "Invalid for this subcommand." get_action(:save).description "Invalid for this subcommand." get_action(:destroy).summary = "Invalid for this subcommand." find = get_action(:find) find.summary "Retrieve info about a resource collection." find.arguments "<collection_name>" find.returns <<-'EOT' A hash of info about the requested resource collection. When used from the Ruby API: returns a Puppet::Resource::Type object. RENDERING ISSUES: yaml and string output for this indirection are currently unusable; use json instead. EOT find.notes <<-'EOT' If two resource collections share the same name (e.g. you have both a node and a class named "default"), `find` will only return one of them. This can be worked around by using `search` instead. EOT find.examples <<-'EOT' Retrieve info about a specific locally-defined class: $ puppet resource_type find ntp::disabled Retrieve info from the puppet master about a specific class: $ puppet resource_type find ntp --terminus rest EOT search = get_action(:search) search.summary "Search for collections matching a regular expression." search.arguments "<regular_expression>" search.returns <<-'EOT' An array of hashes of resource collection info. When used from the Ruby API: returns an array of Puppet::Resource::Type objects. RENDERING ISSUES: yaml and string output for this indirection are currently unusable; use json instead. EOT search.examples <<-'EOT' Retrieve all classes, nodes, and defined types: $ puppet resource_type search '.*' Search for classes related to Nagios: $ puppet resource_type search nagios EOT end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/face/secret_agent.rb��������������������������������������������������������0000664�0052762�0001160�00000003426�12650174557�020730� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/face' Puppet::Face.define(:secret_agent, '0.0.1') do copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" summary "Mimics puppet agent." description <<-'EOT' This subcommand currently functions as a proof of concept, demonstrating how the Faces API exposes Puppet's internal systems to application logic; compare the actual code for puppet agent. It will eventually replace puppet agent entirely, and can provide a template for users who wish to implement agent-like functionality with non-standard application logic. EOT action(:synchronize) do default summary "Run secret_agent once." description <<-'EOT' Mimics a single run of puppet agent. This action does not currently daemonize, but can download plugins, submit facts, retrieve and apply a catalog, and submit a report to the puppet master. EOT returns <<-'EOT' Verbose logging from the completed run. When used from the Ruby API: returns a Puppet::Transaction::Report object. EOT examples <<-'EOT' Trigger a Puppet run with the configured puppet master: $ puppet secret_agent EOT notes <<-'EOT' This action requires that the puppet master's `auth.conf` file allow save access to the `facts` REST terminus. Puppet agent does not use this facility, and it is turned off by default. See <http://docs.puppetlabs.com/guides/rest_auth_conf.html> for more details. EOT when_invoked do |options| Puppet::Face[:plugin, '0.0.1'].download Puppet::Face[:facts, '0.0.1'].upload Puppet::Face[:catalog, '0.0.1'].download report = Puppet::Face[:catalog, '0.0.1'].apply Puppet::Face[:report, '0.0.1'].submit(report) return report end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/face/status.rb��������������������������������������������������������������0000664�0052762�0001160�00000004240�12650174557�017603� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/face' Puppet::Indirector::Face.define(:status, '0.0.1') do copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" summary "View puppet server status." get_action(:destroy).summary "Invalid for this subcommand." get_action(:save).summary "Invalid for this subcommand." get_action(:save).description "Invalid for this subcommand." get_action(:search).summary "Invalid for this subcommand." find = get_action(:find) find.default = true find.summary "Check status of puppet master server." find.arguments "<dummy_text>" 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. Due to a known bug, this action requires a dummy argument, the content of which is irrelevant. 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 <http://docs.puppetlabs.com/guides/rest_auth_conf.html> for more details. Although this action always returns an unnamed status object, it requires a dummy argument. This is a known bug. EOT find.examples <<-'EOT' Check the status of the configured puppet master: $ puppet status find x --terminus rest EOT end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/feature/��������������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�016467� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/feature/base.rb�������������������������������������������������������������0000664�0052762�0001160�00000004750�12650174557�017735� 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! require 'win32ole' # gems require 'win32/process' require 'win32/dir' require 'win32/service' true rescue LoadError => err warn "Cannot run on Microsoft Windows without the win32-process, win32-dir and win32-service gems: #{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 RRD available Puppet.features.add(:rrd_legacy, :libs => ["RRDtool"]) Puppet.features.add(:rrd, :libs => ["RRD"]) # We have OpenSSL Puppet.features.add(:openssl, :libs => ["openssl"]) # We have CouchDB Puppet.features.add(:couchdb, :libs => ["couchrest"]) # 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], :win32_bool true rescue LoadError => err Puppet.debug("CreateSymbolicLink is not available") false end end end WindowsSymlink.is_implemented end end ������������������������puppet-3.8.5/lib/puppet/feature/cfacter.rb����������������������������������������������������������0000664�0052762�0001160�00000000533�12650174557�020425� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'facter' require 'puppet/util/feature' Puppet.features.add :cfacter do begin require 'cfacter' # The first release of cfacter didn't have the necessary interface to work with Puppet # Therefore, if the version is 0.1.0, treat the feature as not present CFacter.version != '0.1.0' rescue LoadError false end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/feature/eventlog.rb���������������������������������������������������������0000664�0052762�0001160�00000000206�12650174557�020636� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' if Puppet.features.microsoft_windows? Puppet.features.add(:eventlog, :libs => %{win32/eventlog}) end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/feature/external_facts.rb���������������������������������������������������0000664�0052762�0001160�00000000142�12650174557�022014� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'facter' Puppet.features.add(:external_facts) { Facter.respond_to?(:search_external) } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/feature/libuser.rb����������������������������������������������������������0000664�0052762�0001160�00000000371�12650174557�020463� 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-3.8.5/lib/puppet/feature/msgpack.rb����������������������������������������������������������0000664�0052762�0001160�00000000123�12650174557�020436� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' Puppet.features.add(:msgpack, :libs => ["msgpack"]) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/feature/pe_license.rb�������������������������������������������������������0000664�0052762�0001160�00000000245�12650174557�021124� 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-3.8.5/lib/puppet/feature/pson.rb�������������������������������������������������������������0000664�0052762�0001160�00000000214�12650174557�017771� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet.features.add(:pson) do Puppet.deprecation_warning "There is no need to check for pson support. It is always available." true end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/feature/rack.rb�������������������������������������������������������������0000664�0052762�0001160�00000000612�12650174557�017734� 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-3.8.5/lib/puppet/feature/rails.rb������������������������������������������������������������0000664�0052762�0001160�00000003142�12650174557�020127� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' Puppet.features.add(:rails) do begin # Turn off the constant watching parts of ActiveSupport, which have a huge # cost in terms of the system watching loaded code to figure out if it was # a missing content, and which we don't actually *use* anywhere. # # In fact, we *can't* depend on the feature: we don't require # ActiveSupport, just load it if we use rails, if we depend on a feature # that it offers. --daniel 2012-07-16 require 'active_support' begin require 'active_support/dependencies' ActiveSupport::Dependencies.unhook! ActiveSupport::Dependencies.mechanism = :require rescue LoadError, ScriptError, StandardError => e # ignore any failure - worst case we run without disabling the CPU # sucking features, so are slower but ... not actually failed, just # because some random future version of ActiveRecord changes. Puppet.debug("disabling ActiveSupport::Dependencies failed: #{e}") end require 'active_record' require 'active_record/version' rescue LoadError if Puppet::FileSystem.exist?("/usr/share/rails") count = 0 Dir.entries("/usr/share/rails").each do |dir| libdir = File.join("/usr/share/rails", dir, "lib") if Puppet::FileSystem.exist?(libdir) and ! $LOAD_PATH.include?(libdir) count += 1 $LOAD_PATH << libdir end end retry if count > 0 end end unless (Puppet::Util.activerecord_version >= 2.1) Puppet.info "ActiveRecord 2.1 or later required for StoreConfigs" false else true end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/feature/rdoc1.rb������������������������������������������������������������0000664�0052762�0001160�00000001362�12650174557�020027� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' # We have a version of RDoc compatible with our module documentation tool. # That is to say, we have the version that comes with Ruby 1.8.7 and earlier, # and not the version that comes with Ruby 1.9.1 or later. # # 1.8 => require 'rdoc/rdoc'; p RDoc::RDoc::VERSION_STRING # => "RDoc V1.0.1 - 20041108" # 1.9 => require 'rdoc'; p RDoc::VERSION # => "3.9.4" # 1.9.2 has 2.5, 1.9.3 has 3.9 # # Anything above that whole 1.0.1 thing is no good for us, and since that # ships with anything in the 1.8 series that we care about (eg: .5, ,7) we can # totally just use that as a proxy for the correct version of rdoc being # available. --daniel 2012-03-08 Puppet.features.add(:rdoc1) { RUBY_VERSION[0,3] == "1.8" } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/feature/rubygems.rb���������������������������������������������������������0000664�0052762�0001160�00000000346�12650174557�020655� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' Puppet.features.add(:rubygems) do Puppet.deprecation_warning "Puppet.features.rubygems? is deprecated. Require rubygems in your application's entry point if you need it." require 'rubygems' end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/feature/selinux.rb����������������������������������������������������������0000664�0052762�0001160�00000000123�12650174557�020500� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' Puppet.features.add(:selinux, :libs => ["selinux"]) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/feature/ssh.rb��������������������������������������������������������������0000664�0052762�0001160�00000000116�12650174557�017610� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' Puppet.features.add(:ssh, :libs => %{net/ssh}) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/feature/stomp.rb������������������������������������������������������������0000664�0052762�0001160�00000000231�12650174557�020153� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' # We want this to load if possible, but it's not automatically # required. Puppet.features.add(:stomp, :libs => %{stomp}) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/feature/zlib.rb�������������������������������������������������������������0000664�0052762�0001160�00000000227�12650174557�017756� 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-3.8.5/lib/puppet/file_bucket.rb��������������������������������������������������������������0000664�0052762�0001160�00000000115�12650174557�017633� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# stub module Puppet::FileBucket class BucketError < RuntimeError; end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/file_bucket/����������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017310� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/file_bucket/dipper.rb�������������������������������������������������������0000664�0052762�0001160�00000007115�12650174557�021125� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'pathname' require 'puppet/file_bucket' require 'puppet/file_bucket/file' require 'puppet/indirector/request' 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] environment = Puppet[:environment] if hash.include?(:Path) @local_path = hash[:Path] @rest_path = nil else @local_path = nil @rest_path = "https://#{server}:#{port}/#{environment}/file_bucket_file/" 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") 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) 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}" Puppet.log_exception(detail, message) raise Puppet::Error, message, detail.backtrace end 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 source_stream = newcontents.stream do |source_stream| FileUtils.copy_stream(source_stream, of) end #of.print(newcontents) } ::File.chmod(changed, file) if changed else Puppet.err "Could not find file with checksum #{sum}" return nil end return newsum else return nil end end private def absolutize_path( path ) Pathname.new(path).realpath end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/file_bucket/file.rb���������������������������������������������������������0000664�0052762�0001160�00000007520�12650174557�020561� 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 [:s, :pson] end def self.default_format # This should really be :raw, like is done for Puppet::FileServing::Content # but this class hasn't historically supported `from_raw`, so switching # would break compatibility between newer 3.x agents talking to older 3.x # masters. However, to/from_s has been supported and achieves the desired # result without breaking compatibility. :s 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}") end @bucket_path = options.delete(:bucket_path) @checksum_type = Puppet[:digest_algorithm].to_sym raise ArgumentError.new("Unknown option(s): #{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 @contents.to_s end def contents to_s end def name "#{checksum_type}/#{checksum_data}" end def self.from_s(contents) self.new(contents) end def to_data_hash # Note that this serializes the entire data to a string and places it in a hash. { "contents" => contents.to_s } end def self.from_data_hash(data) self.new(data["contents"]) end def to_pson Puppet.deprecation_warning("Serializing Puppet::FileBucket::File objects to pson is deprecated.") to_data_hash.to_pson end # This method is deprecated, but cannot be removed for awhile, otherwise # older agents sending pson couldn't backup to filebuckets on newer masters def self.from_pson(pson) Puppet.deprecation_warning("Deserializing Puppet::FileBucket::File objects from pson is deprecated. Upgrade to a newer version.") self.from_data_hash(pson) end private 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_s # 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}") Puppet::Util::Checksums.method(:"#{base_method}_file").call(@path) end def to_s Puppet::FileSystem::binread(@path) end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/file_collection.rb����������������������������������������������������������0000664�0052762�0001160�00000001331�12650174557�020512� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This was a simple way to turn file names into singletons, # # The theory was, like: # 1. Turn filenames into singletons. # 2. ???? # 3. Huge memory savings! # # In practice it used several MB more memory overall, and it cost more CPU # time, and it added complexity to the code. Which was awesome. # # So, I gutted it. It doesn't do anything any more, but we retain the # external form that people included so that they don't explode so much. # # This should be removed from the system after a graceful deprecation period, # probably about the time that a version of Puppet containing this change is # the last supported version. --daniel 2012-07-17 class Puppet::FileCollection require 'puppet/file_collection/lookup' end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/file_collection/������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020166� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/file_collection/lookup.rb���������������������������������������������������0000664�0052762�0001160�00000000603�12650174557�022024� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_collection' module Puppet::FileCollection::Lookup # Yeah, this is all the external interface that was added to the folks who # included this really was. Thankfully. # # See the comments in `puppet/file_collection.rb` for the annotated version, # or just port your code away from this by adding the accessors on your own. attr_accessor :line, :file end �����������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/file_serving.rb�������������������������������������������������������������0000664�0052762�0001160�00000000075�12650174557�020040� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Just a stub class. class Puppet::FileServing # :nodoc: end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/file_serving/���������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017510� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/file_serving/base.rb��������������������������������������������������������0000664�0052762�0001160�00000005132�12650174557�020751� 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(dummy_argument=:work_arround_for_ruby_GC_bug) 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 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 } end def to_pson_data_hash { # No 'document_type' since we don't send these bare 'data' => to_data_hash, 'metadata' => { 'api_version' => 1 } } 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-3.8.5/lib/puppet/file_serving/configuration.rb�����������������������������������������������0000664�0052762�0001160�00000005747�12650174557�022722� 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/pluginfacts' 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}'") unless mount_name =~ %r{^[-\w]+$} raise(ArgumentError, "Cannot find file: Invalid relative 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["pluginfacts"] ||= Mount::PluginFacts.new("pluginfacts") @mounts["pluginfacts"].allow('*') if @mounts["pluginfacts"].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") end ensure # Make sure we've got our plugins and modules. mk_default_mounts end end �������������������������puppet-3.8.5/lib/puppet/file_serving/configuration/�������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�022357� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/file_serving/configuration/parser.rb����������������������������������������0000664�0052762�0001160�00000006430�12650174557�024204� 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 #{@file} does not exist") unless Puppet::FileSystem.exist?(@file) raise("Cannot read file server configuration #{@file}") unless FileTest.readable?(@file) @mounts = {} @count = 0 File.open(@file) { |f| mount = nil f.each_line { |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 raise ArgumentError.new("Invalid argument '#{var}' in #{@file.filename}, line #{@count}") end else raise ArgumentError.new("Invalid line '#{line.chomp}' at #{@file.filename}, line #{@count}") 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" mount.allow(val) rescue Puppet::AuthStoreError => detail raise ArgumentError.new("#{detail.to_s} in #{@file}, line #{@count}") 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" mount.deny(val) rescue Puppet::AuthStoreError => detail raise ArgumentError.new("#{detail.to_s} in #{@file}, line #{@count}") end } end # Create a new mount. def newmount(name) raise ArgumentError.new("#{@mounts[name]} is already mounted at #{name} in #{@file}, line #{@count}") if @mounts.include?(name) case name when "modules" mount = Mount::Modules.new(name) when "plugins" mount = Mount::Plugins.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.name}\": #{detail}") @mounts.delete(mount.name) end else Puppet.warning "The '#{mount.name}' module can not have a path. Ignoring attempt to set it" 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-3.8.5/lib/puppet/file_serving/content.rb�����������������������������������������������������0000664�0052762�0001160�00000002233�12650174557�021510� 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 [:raw] end def self.from_raw(content) instance = new("/this/is/a/fake/path") instance.content = content instance end # BF: we used to fetch the file content here, but this is counter-productive # for puppetmaster streaming of file content. So collect just returns itself def collect(source_permissions = nil) return if stat.ftype == "directory" self 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_raw File.new(full_path, "rb") end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/file_serving/fileset.rb�����������������������������������������������������0000664�0052762�0001160�00000011651�12650174557�021475� 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}") 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) 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 raise(ArgumentError, "Invalid :links value '#{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}'", $!.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). 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-3.8.5/lib/puppet/file_serving/metadata.rb����������������������������������������������������0000664�0052762�0001160�00000013661�12650174557�021625� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/indirector' require 'puppet/file_serving' require 'puppet/file_serving/base' require 'puppet/util/checksums' # 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 PARAM_ORDER = [:mode, :ftype, :owner, :group] def checksum_type=(type) raise(ArgumentError, "Unsupported checksum type #{type}") unless respond_to?("#{type}_file") @checksum_type = type end class MetaStat extend Forwardable def initialize(stat, source_permissions = nil) @stat = stat @source_permissions_ignore = 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 = nil) super(stat, source_permissions) @path = path 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 if @source_permissions_ignore # this code remains for when source_permissions is not set to :ignore begin Puppet::Util::Windows::Security.send("get_#{method}", @path) || default_value rescue Puppet::Util::Windows::Error => detail # Very carefully catch only this specific error that result from # trying to read permissions on a symlinked file that is on a volume # that does not support ACLs. # # Unfortunately readlink method will not return the target path when # the given path is not the symlink. # # For instance, consider: # symlink c:\link points to c:\target # FileSystem.readlink('c:/link') returns 'c:/target' # FileSystem.readlink('c:/link/file') will NOT return 'c:/target/file' # # Since detecting this up front is costly, since the path in question # needs to be recursively split and tested at each depth in the path, # we catch the standard error that will result from trying to read a # file that doesn't have a DACL - 1336 is ERROR_INVALID_DACL # # Note that this affects any manually created symlinks as well as # paths like puppet:///modules return default_value if detail.code == 1336 # Also handle a VirtualBox bug where ERROR_INVALID_FUNCTION is # returned when following a symlink to a volume that is not NTFS. # It appears that the VirtualBox file system is not propagating # the standard Win32 error code above like it should. # # Apologies to all who enter this code path at a later date if detail.code == 1 && Facter.value(:virtual) == 'virtualbox' return default_value end raise end end end end def collect_stat(path, source_permissions) 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, source_permissions) @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 #{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') 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, } ) end def self.from_data_hash(data) new(data.delete('path'), data) end PSON.register_document_type('FileMetadata',self) def to_pson_data_hash { 'document_type' => 'FileMetadata', 'data' => to_data_hash, 'metadata' => { 'api_version' => 1 } } end def to_pson(*args) to_pson_data_hash.to_pson(*args) end def self.from_pson(data) Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.") self.from_data_hash(data) end end �������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/file_serving/mount.rb�������������������������������������������������������0000664�0052762�0001160�00000001435�12650174557�021203� 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}'" 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-3.8.5/lib/puppet/file_serving/mount/���������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020652� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/file_serving/mount/file.rb��������������������������������������������������0000664�0052762�0001160�00000005551�12650174557�022125� 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}") 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" unless FileTest.directory?(path) raise ArgumentError, "#{path} is not readable" 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" # 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-3.8.5/lib/puppet/file_serving/mount/modules.rb�����������������������������������������������0000664�0052762�0001160�00000001145�12650174557�022651� 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-3.8.5/lib/puppet/file_serving/mount/pluginfacts.rb�������������������������������������������0000664�0052762�0001160�00000002306�12650174557�023520� 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 { |mod| mod.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-3.8.5/lib/puppet/file_serving/mount/plugins.rb�����������������������������������������������0000664�0052762�0001160�00000002255�12650174557�022665� 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 { |mod| mod.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-3.8.5/lib/puppet/file_serving/terminus_helper.rb���������������������������������������������0000664�0052762�0001160�00000001414�12650174557�023243� 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 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| inst = model.new(base_path, :relative_path => file) inst.checksum_type = request.options[:checksum_type] if request.options[:checksum_type] inst.links = request.options[:links] if request.options[:links] inst.collect inst end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/file_serving/terminus_selector.rb�������������������������������������������0000664�0052762�0001160�00000001463�12650174557�023610� 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 nil :file_server else raise ArgumentError, "URI protocol '#{request.protocol}' is not currently supported for file serving" end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/file_system.rb��������������������������������������������������������������0000664�0052762�0001160�00000024312�12650174557�017707� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::FileSystem 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 RUBY_VERSION =~ /^1\.8/ require 'puppet/file_system/file18' Puppet::FileSystem::File18 elsif Puppet::Util::Platform.windows? require 'puppet/file_system/file19windows' Puppet::FileSystem::File19Windows else require 'puppet/file_system/file19' Puppet::FileSystem::File19 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. # # @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 aquire 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 [Integer] Extra file operation mode information to use # (defaults to read-only mode) # @param timeout [Integer] Number of seconds to wait for the lock (defaults to 300) # @yield The file handle, in 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) @impl.read(assert_path(path)) 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 # 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-3.8.5/lib/puppet/file_system/����������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017357� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/file_system/file18.rb�������������������������������������������������������0000664�0052762�0001160�00000000211�12650174557�020767� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::FileSystem::File18 < Puppet::FileSystem::FileImpl def binread(path) ::File.open(path, 'rb') { |f| f.read } end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/file_system/file19.rb�������������������������������������������������������0000664�0052762�0001160�00000002201�12650174557�020771� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::FileSystem::File19 < 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-3.8.5/lib/puppet/file_system/file19windows.rb������������������������������������������������0000664�0052762�0001160�00000006257�12650174557�022423� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_system/file19' require 'puppet/util/windows' class Puppet::FileSystem::File19Windows < Puppet::FileSystem::File19 def exist?(path) if ! Puppet.features.manages_symlinks? return ::File.exist?(path) end path = path.to_str if path.respond_to?(:to_str) # support WatchedFile path = path.to_s # support String and Pathname begin if Puppet::Util::Windows::File.symlink?(path) path = Puppet::Util::Windows::File.readlink(path) end ! Puppet::Util::Windows::File.stat(path).nil? rescue # generally INVALID_HANDLE_VALUE which means 'file not found' false end 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") 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 && 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-3.8.5/lib/puppet/file_system/file_impl.rb����������������������������������������������������0000664�0052762�0001160�00000005161�12650174557�021650� 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: '#{path.class}'" end end def path_string(path) path.to_s 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}" 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) path.read 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-3.8.5/lib/puppet/file_system/memory_file.rb��������������������������������������������������0000664�0052762�0001160�00000003047�12650174557�022220� 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 # Used by Ruby 1.8.7 file system abstractions when operating on Pathname like things. def to_str to_path end def inspect "<Puppet::FileSystem::MemoryFile:#{to_s}>" end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/file_system/memory_impl.rb��������������������������������������������������0000664�0052762�0001160�00000002563�12650174557�022244� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::FileSystem::MemoryImpl def initialize(*files) @files = files + all_children_of(files) 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) 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.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-3.8.5/lib/puppet/file_system/path_pattern.rb�������������������������������������������������0000664�0052762�0001160�00000004353�12650174557�022403� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'pathname' 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-3.8.5/lib/puppet/file_system/uniquefile.rb���������������������������������������������������0000664�0052762�0001160�00000007763�12650174557�022070� 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: #{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}'" end path end def try_convert_to_hash(h) begin h.to_hash rescue NoMethodError => e nil end end @@systmpdir ||= defined?(Etc.systmpdir) ? Etc.systmpdir : '/tmp' def tmpdir tmp = '.' if $SAFE > 0 tmp = @@systmpdir else for dir in [ENV['TMPDIR'], ENV['TMP'], 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 lock end def mkdir(*args) Dir.mkdir(*args) end def rmdir(*args) Dir.rmdir(*args) end end end�������������puppet-3.8.5/lib/puppet/forge.rb��������������������������������������������������������������������0000664�0052762�0001160�00000015013�12650174557�016464� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/vendor' Puppet::Vendor.load_vendored require 'net/http' require 'tempfile' require 'uri' require 'pathname' require 'json' require 'semantic' class Puppet::Forge < Semantic::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 attr_reader :host, :repository def initialize(host = Puppet[:module_repository]) @host = host @repository = Puppet::Forge::Repository.new(host, USER_AGENT) 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" => "http://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=#{URI.escape(term)}" if Puppet[:module_groups] uri += "&module_groups=#{Puppet[:module_groups]}" end while uri response = make_http_request(uri) if response.code == '200' result = JSON.parse(response.body) 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<Semantic::Dependency::ModuleRelease>] a list of releases for # the given name # @see Semantic::Dependency::Source#fetch def fetch(input) name = input.tr('/', '-') uri = "/v3/releases?module=#{name}" if Puppet[:module_groups] uri += "&module_groups=#{Puppet[:module_groups]}" end releases = [] while uri response = make_http_request(uri) if response.code == '200' response = JSON.parse(response.body) else raise ResponseError.new(:uri => URI.parse(@host).merge(uri), :response => response) end releases.concat(process(response['results'])) uri = response['pagination']['next'] end return releases end def make_http_request(*args) @repository.make_http_request(*args) end class ModuleRelease < Semantic::Dependency::ModuleRelease attr_reader :install_dir, :metadata def initialize(source, data) @data = data @metadata = meta = data['metadata'] name = meta['name'].tr('/', '-') version = Semantic::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)[0..1] rescue ArgumentError => e raise ArgumentError, "Malformed dependency: #{dep['name']}. Exception was: #{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 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" 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: #{e.message}" end end end private def process(list) l = list.map do |release| metadata = release['metadata'] begin ModuleRelease.new(self, release) rescue ArgumentError => e Puppet.warning "Cannot consider release #{metadata['name']}-#{metadata['version']}: #{e}" false end end l.select { |r| r } end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/forge/����������������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�016136� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/forge/cache.rb��������������������������������������������������������������0000664�0052762�0001160�00000003114�12650174557�017526� 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' 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-3.8.5/lib/puppet/forge/errors.rb�������������������������������������������������������������0000664�0052762�0001160�00000007002�12650174557�017777� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require '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}", original) end # Return a multiline version of the error message # # @return [String] the multiline version of the error message def multiline <<-EOS.chomp Could not connect via HTTPS to #{@uri} 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 # 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}." super(message, original) end # Return a multiline version of the error message # # @return [String] the multiline version of the error message def multiline <<-EOS.chomp Could not connect to #{@uri} There was a network communications problem The error we caught said '#{@detail}' Check your network connection and try again EOS 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 = JSON.parse(response.body) if body['message'] @message ||= body['message'].strip end rescue JSON::ParserError end message = "Request to Puppet Forge failed. Detail: " message << @message << " / " if @message message << @response << "." super(message, original) end # Return a multiline version of the error message # # @return [String] the multiline version of the error message def multiline message = <<-EOS.chomp Request to Puppet Forge failed. The server being queried was #{@uri} The HTTP response we received was '#{@response}' EOS message << "\n The message we received said '#{@message}'" if @message message end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/forge/repository.rb���������������������������������������������������������0000664�0052762�0001160�00000012601�12650174557�020703� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'net/https' require 'digest/sha1' require 'uri' require 'puppet/util/http_proxy' require 'puppet/forge' require 'puppet/forge/errors' if Puppet.features.zlib? && Puppet[:zlib] require 'zlib' end 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? && Puppet[: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) Puppet.debug "HTTP GET #{@host}#{path}" request = get_request_object(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 def get_request_object(path) headers = { "User-Agent" => user_agent, } if Puppet.features.zlib? && Puppet[:zlib] && RUBY_VERSION >= "1.9" headers = headers.merge({ "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", }) end if forge_authorization headers = headers.merge({"Authorization" => forge_authorization}) end request = Net::HTTP::Get.new(URI.escape(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 = get_http_object http_object.start do |http| response = http.request(request) if Puppet.features.zlib? && Puppet[: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 a Net::HTTP::Proxy object constructed from the settings provided # accessing the repository. # # 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. # # @return [Net::HTTP::Proxy] object constructed from repo settings def get_http_object proxy_class = Net::HTTP::Proxy(Puppet::Util::HttpProxy.http_proxy_host, Puppet::Util::HttpProxy.http_proxy_port, Puppet::Util::HttpProxy.http_proxy_user, Puppet::Util::HttpProxy.http_proxy_password) proxy = proxy_class.new(@uri.host, @uri.port) 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 proxy 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/#{Puppet.version}", "Ruby/#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})", ].join(' ').freeze end end end �������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/functions.rb����������������������������������������������������������������0000664�0052762�0001160�00000057125�12650174557�017404� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# @note WARNING: This new function API is still under development and may change at any time # # 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/<modulename>` directory, where `<modulename>` 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 corrosponding method is called. # # 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 # # 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<Function>] the newly created Function class # # @api public def self.create_function(func_name, 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}" 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. # the_class = Class.new(function_base, &block) # Make the anonymous class appear to have the class-name <func_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 end # 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] == Puppet::Pops::Types::INFINITY) the_class.dispatcher.add_dispatch(type, simple_name, names, nil, nil, nil, 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" end any_signature(*min_max_param(the_class.instance_method(func_name))) end # @api private def self.min_max_param(method) # Ruby 1.8.7 does not have support for details about parameters if method.respond_to?(:parameters) result = {:req => 0, :opt => 0, :rest => 0 } # TODO: Optimize into one map iteration that produces names map, and sets # count as side effect method.parameters.each { |p| result[p[0]] += 1 } from = result[:req] to = result[:rest] > 0 ? :default : from + result[:opt] names = method.parameters.map {|p| p[1].to_s } else # Cannot correctly compute the signature in Ruby 1.8.7 because arity for # optional values is screwed up (there is no way to get the upper limit), # an optional looks the same as a varargs In this case - the failure will # simply come later when the call fails # arity = method.arity from = arity >= 0 ? arity : -arity -1 to = arity >= 0 ? arity : :default # i.e. infinite (which is wrong when there are optional - flaw in 1.8.7) names = [] # no names available end [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] factory = Puppet::Pops::Types::TypeFactory [factory.callable(factory.any, from, to), 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 @type_parser ||= Puppet::Pops::Types::TypeParser.new @all_callables ||= Puppet::Pops::Types::TypeFactory.all_callables DispatcherBuilder.new(dispatcher, @type_parser, @all_callables) 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, &block) end 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 # @api private def initialize(dispatcher, type_parser, all_callables) @type_parser = type_parser @all_callables = all_callables @dispatcher = dispatcher 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 # the type must be an independent instance since it will be contained in another type type = @all_callables.copy name = 'block' when 1 # the type must be an independent instance since it will be contained in another type type = @all_callables.copy name = type_and_name[0] when 2 type_string, name = type_and_name type = @type_parser.parse(type_string) else raise ArgumentError, "block_param accepts max 2 arguments (type, name), got #{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}" end unless name.is_a?(String) || name.is_a?(Symbol) raise ArgumentError, "Expected block_param name to be a String or Symbol, got #{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 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 type.is_a?(String) @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}" end end # @api private def dispatch(meth_name, &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 self.instance_eval &block callable_t = create_callable(@types, @block_type, @min, @max) @dispatcher.add_dispatch(callable_t, meth_name, @names, @block_name, @injections, @weaving, @max == :default) 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, from, to) mapped_types = types.map do |t| @type_parser.parse(t) end if from != to # :optional and/or :repeated parameters are present. mapped_types << from mapped_types << to end if block_type mapped_types << block_type end Puppet::Pops::Types::TypeFactory.callable(*mapped_types) end end private # @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. # # Injection Support # === # The Function API supports injection of data and services. It is possible to # make injection that takes effect when the function is loaded (for services # and runtime configuration that does not change depending on how/from where # in what context the function is called. It is also possible to inject and # weave argument values into a call. # # Injection of attributes # --- # Injection of attributes is performed by one of the methods `attr_injected`, # and `attr_injected_producer`. The injected attributes are available via # accessor method calls. # # @example using injected attributes # Puppet::Functions.create_function('test') do # attr_injected String, :larger, 'message_larger' # attr_injected String, :smaller, 'message_smaller' # def test(a, b) # a > b ? larger() : smaller() # end # end # # @api private class InternalFunction < Function # @api private def self.builder @type_parser ||= Puppet::Pops::Types::TypeParser.new @all_callables ||= Puppet::Pops::Types::TypeFactory.all_callables InternalDispatchBuilder.new(dispatcher, @type_parser, @all_callables) end # Defines class level injected attribute with reader method # # @api private def self.attr_injected(type, attribute_name, injection_name = nil) define_method(attribute_name) do ivar = :"@#{attribute_name.to_s}" unless instance_variable_defined?(ivar) injector = Puppet.lookup(:injector) instance_variable_set(ivar, injector.lookup(closure_scope, type, injection_name)) end instance_variable_get(ivar) end end # Defines class level injected producer attribute with reader method # # @api private def self.attr_injected_producer(type, attribute_name, injection_name = nil) define_method(attribute_name) do ivar = :"@#{attribute_name.to_s}" unless instance_variable_defined?(ivar) injector = Puppet.lookup(:injector) instance_variable_set(ivar, injector.lookup_producer(closure_scope, type, injection_name)) end instance_variable_get(ivar) end 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) internal_call_function(scope, function_name, args) 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. # # Injection and Weaving of parameters # --- # It is possible to inject and weave 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' # injected_param 'String', 'larger', 'message_larger' # injected_param 'String', 'smaller', 'message_smaller' # end # def test(a, b, larger, smaller) # a > b ? larger : smaller # end # end # # The function in the example above is called like this: # # test(10, 20) # # Using injected value as default # --- # Default value assignment is handled by using the regular Ruby mechanism (a # value is assigned to the variable). The dispatch simply indicates that the # value is optional. If the default value should be injected, it can be # handled different ways depending on what is desired: # # * by calling the accessor method for an injected Function class attribute. # This is suitable if the value is constant across all instantiations of the # function, and across all calls. # * by injecting a parameter into the call # to the left of the parameter, and then assigning that as the default value. # * One of the above forms, but using an injected producer instead of a # directly injected value. # # @example method with injected default values # Puppet::Functions.create_function('test') do # dispatch :test do # injected_param String, 'b_default', 'b_default_value_key' # param 'Scalar', 'a' # param 'Scalar', 'b' # end # def test(b_default, a, b = b_default) # # ... # end # end # # @api private class InternalDispatchBuilder < DispatcherBuilder def scope_param() @injections << [:scope, 'scope', '', :dispatcher_internal] # mark what should be picked for this position when dispatching @weaving << [@injections.size()-1] end # TODO: is param name really needed? Perhaps for error messages? (it is unused now) # # @api private def injected_param(type, name, injection_name = '') @injections << [type, name, injection_name] # mark what should be picked for this position when dispatching @weaving << [@injections.size() -1] end # TODO: is param name really needed? Perhaps for error messages? (it is unused now) # # @api private def injected_producer_param(type, name, injection_name = '') @injections << [type, name, injection_name, :producer] # mark what should be picked for this position when dispatching @weaving << [@injections.size()-1] end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/functions/������������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017044� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/functions/assert_type.rb����������������������������������������������������0000664�0052762�0001160�00000004566�12650174557�021747� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Returns the given value if it is an instance of the given type, and raises an error otherwise. # Optionally, if a block is given (accepting two parameters), it will be called instead of raising # an error. This to enable giving the user richer feedback, or to supply a default value. # # @example how to assert type # # assert that `$b` is a non empty `String` and assign to `$a` # $a = assert_type(String[1], $b) # # @example using custom error message # $a = assert_type(String[1], $b) |$expected, $actual| { fail("The name cannot be empty") } # # @example using a warning and a default # $a = assert_type(String[1], $b) |$expected, $actual| { warning("Name is empty, using default") 'anonymous' } # # See the documentation for "The Puppet Type System" for more information about types. # 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(value) if block_given? # Give the inferred type to allow richer comparisson in the given block (if generalized # information is lost). # value = yield(type, inferred_type) else # Do not give all the details - i.e. format as Integer, instead of Integer[n, n] for exact value, which # is just confusing. (OTOH: may need to revisit, or provide a better "type diff" output. # actual = Puppet::Pops::Types::TypeCalculator.generalize!(inferred_type) raise Puppet::ParseError, "assert_type(): Expected type #{type} does not match actual: #{actual}" 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.new.parse(type_string) block_given? ? assert_type(t, value, &proc) : assert_type(t, value) end end ������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/functions/defined.rb��������������������������������������������������������0000664�0052762�0001160�00000012404�12650174557�020771� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Determines whether # a given class or resource type is defined. This function can also determine whether a # specific resource has been declared, or whether a variable has been assigned a value # (including undef...as opposed to never having been assigned anything). Returns true # or false. Accepts class names, type names, resource references, and variable # reference strings of the form '$name'. When more than one argument is # supplied, defined() returns true if any are defined. # # The `defined` function checks both native and defined types, including types # provided as plugins via modules. Types and classes are both checked using their names: # # defined("file") # defined("customtype") # defined("foo") # defined("foo::bar") # defined('$name') # # Resource declarations are checked using resource references, e.g. # `defined( File['/tmp/myfile'] )`, or `defined( Class[myclass] )`. # Checking whether a given resource # has been declared is, unfortunately, dependent on the evaluation order of # the configuration, and the following code will not work: # # if defined(File['/tmp/foo']) { # notify { "This configuration includes the /tmp/foo file.":} # } # file { "/tmp/foo": # ensure => present, # } # # However, this order requirement refers to evaluation order only, and ordering of # resources in the configuration graph (e.g. with `before` or `require`) does not # affect the behavior of `defined`. # # You may also search using types: # # defined(Resource['file','/some/file']) # defined(File['/some/file']) # defined(Class['foo']) # # The `defined` function does not answer if data types (e.g. `Integer`) are defined. If # given the string 'integer' the result is false, and if given a non CatalogEntry type, # an error is raised. # # The rules for asking for undef, empty strings, and the main class are different from 3.x # (non future parser) and 4.x (with future parser or in Puppet 4.0.0 and later): # # defined('') # 3.x => true, 4.x => false # defined(undef) # 3.x => true, 4.x => error # defined('main') # 3.x => false, 4.x => true # # With the future parser, it is also possible to ask specifically if a name is # a resource type (built in or defined), or a class, by giving its type: # # defined(Type[Class['foo']]) # defined(Type[Resource['foo']]) # # Which is different from asking: # # defined('foo') # # Since the later returns true if 'foo' is either a class, a built-in resource type, or a user defined # resource type, and a specific request like `Type[Class['foo']]` only returns true if `'foo'` is a class. # # @since 2.7.0 # @since 3.6.0 variable reference and future parser types") # @since 3.8.1 type specific requests with future parser # 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 scope.find_hostclass('') else # Find a resource type, definition or class definition scope.find_resource_type(val) || scope.find_definition(val) || scope.find_hostclass(val) #scope.compiler.findresource(:class, val) end end when Puppet::Resource # Find instance of given resource type and title that is in the catalog scope.compiler.findresource(val.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? if val.title.nil? scope.find_builtin_resource_type(val.type_name) || scope.find_definition(val.type_name) else scope.compiler.findresource(val.type_name, val.title) end when Puppet::Pops::Types::PHostClassType 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::PType 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::PHostClassType # 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? scope.find_hostclass(val.type.class_name) end else raise ArgumentError, "Invalid argument of type '#{val.class}' to 'defined'" end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/functions/each.rb�����������������������������������������������������������0000664�0052762�0001160�00000007037�12650174557�020301� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Applies a parameterized block to each element in a sequence of selected entries from the first # argument and returns the first argument. # # This function takes two mandatory arguments: the first should be an Array or a Hash or something that is # of enumerable type (integer, Integer range, or String), and the second # a parameterized block as produced by the puppet syntax: # # $a.each |$x| { ... } # each($a) |$x| { ... } # # When the first argument is an Array (or of enumerable type other than Hash), the parameterized block # should define one or two block parameters. # For each application of the block, the next element from the array is selected, and it is passed to # the block if the block has one parameter. If the block has two parameters, the first is the elements # index, and the second the value. The index starts from 0. # # $a.each |$index, $value| { ... } # each($a) |$index, $value| { ... } # # When the first argument is a Hash, the parameterized block should define one or two parameters. # When one parameter is defined, the iteration is performed with each entry as an array of `[key, value]`, # and when two parameters are defined the iteration is performed with key and value. # # $a.each |$entry| { ..."key ${$entry[0]}, value ${$entry[1]}" } # $a.each |$key, $value| { ..."key ${key}, value ${value}" } # # @example using each # # [1,2,3].each |$val| { ... } # 1, 2, 3 # [5,6,7].each |$index, $val| { ... } # (0, 5), (1, 6), (2, 7) # {a=>1, b=>2, c=>3}].each |$val| { ... } # ['a', 1], ['b', 2], ['c', 3] # {a=>1, b=>2, c=>3}.each |$key, $val| { ... } # ('a', 1), ('b', 2), ('c', 3) # Integer[ 10, 20 ].each |$index, $value| { ... } # (0, 10), (1, 11) ... # "hello".each |$char| { ... } # 'h', 'e', 'l', 'l', 'o' # 3.each |$number| { ... } # 0, 1, 2 # # @since 3.2 for Array and Hash # @since 3.5 for other enumerables # @note requires `parser = future` # 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 'Any', :enumerable block_param 'Callable[2,2]', :block end dispatch :foreach_Enumerable_1 do param 'Any', :enumerable block_param 'Callable[1,1]', :block end def foreach_Hash_1(hash) enumerator = hash.each_pair hash.size.times do yield(enumerator.next) end # produces the receiver hash end def foreach_Hash_2(hash) enumerator = hash.each_pair hash.size.times do yield(*enumerator.next) end # produces the receiver hash end def foreach_Enumerable_1(enumerable) enum = asserted_enumerable(enumerable) begin loop { yield(enum.next) } rescue StopIteration end # produces the receiver enumerable end def foreach_Enumerable_2(enumerable) enum = asserted_enumerable(enumerable) index = 0 begin loop do yield(index, enum.next) index += 1 end rescue StopIteration end # produces the receiver enumerable end def asserted_enumerable(obj) unless enum = Puppet::Pops::Types::Enumeration.enumerator(obj) raise ArgumentError, ("#{self.class.name}(): wrong argument type (#{obj.class}; must be something enumerable.") end enum end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/functions/epp.rb������������������������������������������������������������0000664�0052762�0001160�00000005655�12650174557�020171� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Evaluates an Embedded Puppet Template (EPP) file and returns the rendered text result as a String. # # The first 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.epp` will load the # file `<MODULES DIRECTORY>/apache/templates/vhost.conf.epp`.) # # The second argument is optional; if present, it should be a hash containing parameters for the # template. (See below.) # # EPP supports the following tags: # # * `<%= puppet expression %>` - This tag renders the value of the expression it contains. # * `<% puppet expression(s) %>` - This tag will execute the expression(s) it contains, but renders nothing. # * `<%# comment %>` - The tag and its content renders nothing. # * `<%%` or `%%>` - Renders a literal `<%` or `%>` respectively. # * `<%-` - Same as `<%` but suppresses any leading whitespace. # * `-%>` - Same as `%>` but suppresses any trailing whitespace on the same line (including line break). # * `<%- |parameters| -%>` - When placed as the first tag declares the template's parameters. # # File based EPP supports the following visibilities of variables in scope: # # * Global scope (i.e. top + node scopes) - global scope is always visible # * Global + all given arguments - if the EPP template does not declare parameters, and arguments are given # * Global + declared parameters - if the EPP declares parameters, given argument names must match # # EPP supports parameters by placing an optional parameter list as the very first element in the EPP. As an example, # `<%- |$x, $y, $z = 'unicorn'| -%>` when placed first in the EPP text declares that the parameters `x` and `y` must be # given as template arguments when calling `inline_epp`, and that `z` if not given as a template argument # defaults to `'unicorn'`. Template parameters are available as variables, e.g.arguments `$x`, `$y` and `$z` in the example. # Note that `<%-` must be used or any leading whitespace will be interpreted as text # # Arguments are passed to the template by calling `epp` with a Hash as the last argument, where parameters # are bound to values, e.g. `epp('...', {'x'=>10, 'y'=>20})`. Excess arguments may be given # (i.e. undeclared parameters) only if the EPP templates does not declare any parameters at all. # Template parameters shadow variables in outer scopes. File based epp does never have access to variables in the # scope where the `epp` function is called from. # # @see function inline_epp for examples of EPP # @since 3.5 # @note Requires Future Parser 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-3.8.5/lib/puppet/functions/filter.rb���������������������������������������������������������0000664�0052762�0001160�00000006431�12650174557�020663� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Applies a parameterized block to each element in a sequence of entries from the first # argument and returns an array or hash (same type as left operand for array/hash, and array for # other enumerable types) with the entries for which the block evaluates to `true`. # # This function takes two mandatory arguments: the first should be an Array, a Hash, or an # Enumerable object (integer, Integer range, or String), # and the second a parameterized block as produced by the puppet syntax: # # $a.filter |$x| { ... } # filter($a) |$x| { ... } # # When the first argument is something other than a Hash, the block is called with each entry in turn. # When the first argument is a Hash the entry is an array with `[key, value]`. # # @example Using filter with one parameter # # # selects all that end with berry # $a = ["raspberry", "blueberry", "orange"] # $a.filter |$x| { $x =~ /berry$/ } # rasberry, blueberry # # If the block defines two parameters, they will be set to `index, value` (with index starting at 0) for all # enumerables except Hash, and to `key, value` for a Hash. # # @example Using filter with two parameters # # # selects all that end with 'berry' at an even numbered index # $a = ["raspberry", "blueberry", "orange"] # $a.filter |$index, $x| { $index % 2 == 0 and $x =~ /berry$/ } # raspberry # # # selects all that end with 'berry' and value >= 1 # $a = {"raspberry"=>0, "blueberry"=>1, "orange"=>1} # $a.filter |$key, $x| { $x =~ /berry$/ and $x >= 1 } # blueberry # # @since 3.4 for Array and Hash # @since 3.5 for other enumerables # @note requires `parser = future` # 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 'Any', :enumerable block_param 'Callable[2,2]', :block end dispatch :filter_Enumerable_1 do param 'Any', :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 = [] index = 0 enum = asserted_enumerable(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) result = [] index = 0 enum = asserted_enumerable(enumerable) begin loop do it = enum.next if yield(index, it) == true result << it end index += 1 end rescue StopIteration end result end def asserted_enumerable(obj) unless enum = Puppet::Pops::Types::Enumeration.enumerator(obj) raise ArgumentError, ("#{self.class.name}(): wrong argument type (#{obj.class}; must be something enumerable.") end enum end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/functions/hiera.rb����������������������������������������������������������0000664�0052762�0001160�00000002504�12650174557�020463� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'hiera/puppet_function' # Performs a standard priority lookup and returns the most specific value for a given key. # The returned value can be data of any type (strings, arrays, or hashes). # # The function can be called in one of three ways: # 1. Using 1 to 3 arguments where the arguments are: # 'key' [String] Required # The key to lookup. # 'default` [Any] Optional # A value to return when there's no match for `key`. Optional # `override` [Any] Optional # An argument in the third position, providing a data source # to consult for matching values, even if it would not ordinarily be # part of the matched hierarchy. If Hiera doesn't find a matching key # in the named override data source, it will continue to search through the # rest of the hierarchy. # # 2. Using a 'key' and an optional 'override' parameter like in #1 but with a block to # provide the default value. The block is called with one parameter (the key) and # should return the value. # # 3. Like #1 but with all arguments passed in an array. # # More thorough examples of `hiera` are available at: # <http://docs.puppetlabs.com/hiera/1/puppet.html#hiera-lookup-functions> Puppet::Functions.create_function(:hiera, Hiera::PuppetFunction) do init_dispatch end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/functions/hiera_array.rb����������������������������������������������������0000664�0052762�0001160�00000002743�12650174557�021666� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'hiera/puppet_function' # Returns all matches throughout the hierarchy --- not just the first match --- as a flattened array of unique values. # If any of the matched values are arrays, they're flattened and included in the results. # # The function can be called in one of three ways: # 1. Using 1 to 3 arguments where the arguments are: # 'key' [String] Required # The key to lookup. # 'default` [Any] Optional # A value to return when there's no match for `key`. Optional # `override` [Any] Optional # An argument in the third position, providing a data source # to consult for matching values, even if it would not ordinarily be # part of the matched hierarchy. If Hiera doesn't find a matching key # in the named override data source, it will continue to search through the # rest of the hierarchy. # # 2. Using a 'key' and an optional 'override' parameter like in #1 but with a block to # provide the default value. The block is called with one parameter (the key) and # should return the value. # # 3. Like #1 but with all arguments passed in an array. # If any matched value is a hash, puppet will raise a type mismatch error. # # More thorough examples of `hiera` are available at: # <http://docs.puppetlabs.com/hiera/1/puppet.html#hiera-lookup-functions> Puppet::Functions.create_function(:hiera_array, Hiera::PuppetFunction) do init_dispatch def merge_type :array end end �����������������������������puppet-3.8.5/lib/puppet/functions/hiera_hash.rb�����������������������������������������������������0000664�0052762�0001160�00000003203�12650174557�021463� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'hiera/puppet_function' # Returns a merged hash of matches from throughout the hierarchy. In cases where two or # more hashes share keys, the hierarchy order determines which key/value pair will be # used in the returned hash, with the pair in the highest priority data source winning. # # The function can be called in one of three ways: # 1. Using 1 to 3 arguments where the arguments are: # 'key' [String] Required # The key to lookup. # 'default` [Any] Optional # A value to return when there's no match for `key`. Optional # `override` [Any] Optional # An argument in the third position, providing a data source # to consult for matching values, even if it would not ordinarily be # part of the matched hierarchy. If Hiera doesn't find a matching key # in the named override data source, it will continue to search through the # rest of the hierarchy. # # 2. Using a 'key' and an optional 'override' parameter like in #1 but with a block to # provide the default value. The block is called with one parameter (the key) and # should return the value. # # 3. Like #1 but with all arguments passed in an array. # `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 will raise a type mismatch error. # # More thorough examples of `hiera_hash` are available at: # <http://docs.puppetlabs.com/hiera/1/puppet.html#hiera-lookup-functions> Puppet::Functions.create_function(:hiera_hash, Hiera::PuppetFunction) do init_dispatch def merge_type :hash end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/functions/hiera_include.rb��������������������������������������������������0000664�0052762�0001160�00000004206�12650174557�022167� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'hiera/puppet_function' # Assigns classes to a node using an array merge lookup that retrieves the value for a user-specified key # from a Hiera data source. # # To use `hiera_include`, the following configuration is required: # - A key name to use for classes, e.g. `classes`. # - A line in the puppet `sites.pp` file (e.g. `/etc/puppet/manifests/sites.pp`) # reading `hiera_include('classes')`. Note that this line must be outside any node # definition and below any top-scope variables in use for Hiera lookups. # - Class keys in the appropriate data sources. In a data source keyed to a node's role, # one might have: # # --- # classes: # - apache # - apache::passenger # The function can be called in one of three ways: # 1. Using 1 to 3 arguments where the arguments are: # 'key' [String] Required # The key to lookup. # 'default` [Any] Optional # A value to return when there's no match for `key`. Optional # `override` [Any] Optional # An argument in the third position, providing a data source # to consult for matching values, even if it would not ordinarily be # part of the matched hierarchy. If Hiera doesn't find a matching key # in the named override data source, it will continue to search through the # rest of the hierarchy. # # 2. Using a 'key' and an optional 'override' parameter like in #1 but with a block to # provide the default value. The block is called with one parameter (the key) and # should return the array to be used in the subsequent call to include. # # 3. Like #1 but with all arguments passed in an array. # # More thorough examples of `hiera_include` are available at: # <http://docs.puppetlabs.com/hiera/1/puppet.html#hiera-lookup-functions> Puppet::Functions.create_function(:hiera_include, Hiera::PuppetFunction) do init_dispatch def merge_type :array end def post_lookup(scope, key, value) raise Puppet::ParseError, "Could not find data item #{key}" if value.nil? call_function_with_scope(scope, 'include', value) unless value.empty? end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/functions/import.rb���������������������������������������������������������0000664�0052762�0001160�00000000414�12650174557�020703� 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-3.8.5/lib/puppet/functions/inline_epp.rb�����������������������������������������������������0000664�0052762�0001160�00000007506�12650174557�021524� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Evaluates an Embedded Puppet Template (EPP) string and returns the rendered text result as a String. # # EPP support the following tags: # # * `<%= puppet expression %>` - This tag renders the value of the expression it contains. # * `<% puppet expression(s) %>` - This tag will execute the expression(s) it contains, but renders nothing. # * `<%# comment %>` - The tag and its content renders nothing. # * `<%%` or `%%>` - Renders a literal `<%` or `%>` respectively. # * `<%-` - Same as `<%` but suppresses any leading whitespace. # * `-%>` - Same as `%>` but suppresses any trailing whitespace on the same line (including line break). # * `<%- |parameters| -%>` - When placed as the first tag declares the template's parameters. # # Inline EPP supports the following visibilities of variables in scope which depends on how EPP parameters # are used - see further below: # # * Global scope (i.e. top + node scopes) - global scope is always visible # * Global + Enclosing scope - if the EPP template does not declare parameters, and no arguments are given # * Global + all given arguments - if the EPP template does not declare parameters, and arguments are given # * Global + declared parameters - if the EPP declares parameters, given argument names must match # # EPP supports parameters by placing an optional parameter list as the very first element in the EPP. As an example, # `<%- |$x, $y, $z='unicorn'| -%>` when placed first in the EPP text declares that the parameters `x` and `y` must be # given as template arguments when calling `inline_epp`, and that `z` if not given as a template argument # defaults to `'unicorn'`. Template parameters are available as variables, e.g.arguments `$x`, `$y` and `$z` in the example. # Note that `<%-` must be used or any leading whitespace will be interpreted as text # # Arguments are passed to the template by calling `inline_epp` with a Hash as the last argument, where parameters # are bound to values, e.g. `inline_epp('...', {'x'=>10, 'y'=>20})`. Excess arguments may be given # (i.e. undeclared parameters) only if the EPP templates does not declare any parameters at all. # Template parameters shadow variables in outer scopes. # # Note: An inline template is best stated using a single-quoted string, or a heredoc since a double-quoted string # is subject to expression interpolation before the string is parsed as an EPP template. Here are examples # (using heredoc to define the EPP text): # # @example Various Examples using `inline_epp` # # # produces 'Hello local variable world!' # $x ='local variable' # inline_epptemplate(@(END:epp)) # <%- |$x| -%> # Hello <%= $x %> world! # END # # # produces 'Hello given argument world!' # $x ='local variable world' # inline_epptemplate(@(END:epp), { x =>'given argument'}) # <%- |$x| -%> # Hello <%= $x %> world! # END # # # produces 'Hello given argument world!' # $x ='local variable world' # inline_epptemplate(@(END:epp), { x =>'given argument'}) # <%- |$x| -%> # Hello <%= $x %>! # END # # # results in error, missing value for y # $x ='local variable world' # inline_epptemplate(@(END:epp), { x =>'given argument'}) # <%- |$x, $y| -%> # Hello <%= $x %>! # END # # # Produces 'Hello given argument planet' # $x ='local variable world' # inline_epptemplate(@(END:epp), { x =>'given argument'}) # <%- |$x, $y=planet| -%> # Hello <%= $x %> <%= $y %>! # END # # @since 3.5 # @note Requires Future Parser # 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-3.8.5/lib/puppet/functions/map.rb������������������������������������������������������������0000664�0052762�0001160�00000005237�12650174557�020156� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Applies a parameterized block to each element in a sequence of entries from the first # argument and returns an array with the result of each invocation of the parameterized block. # # This function takes two mandatory arguments: the first should be an Array, Hash, or of Enumerable type # (integer, Integer range, or String), and the second a parameterized block as produced by the puppet syntax: # # $a.map |$x| { ... } # map($a) |$x| { ... } # # When the first argument `$a` is an Array or of enumerable type, the block is called with each entry in turn. # When the first argument is a hash the entry is an array with `[key, value]`. # # @example Using map with two arguments # # # Turns hash into array of values # $a.map |$x|{ $x[1] } # # # Turns hash into array of keys # $a.map |$x| { $x[0] } # # When using a block with 2 parameters, the element's index (starting from 0) for an array, and the key for a hash # is given to the block's first parameter, and the value is given to the block's second parameter.args. # # @example Using map with two arguments # # # Turns hash into array of values # $a.map |$key,$val|{ $val } # # # Turns hash into array of keys # $a.map |$key,$val|{ $key } # # @since 3.4 for Array and Hash # @since 3.5 for other enumerables, and support for blocks with 2 parameters # @note requires `parser = future` # 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 'Any', :enumerable block_param 'Callable[2,2]', :block end dispatch :map_Enumerable_1 do param 'Any', :enumerable block_param 'Callable[1,1]', :block end def map_Hash_1(hash) hash.map {|x, y| yield([x, y]) } end def map_Hash_2(hash) hash.map {|x, y| yield(x, y) } end def map_Enumerable_1(enumerable) result = [] index = 0 enum = asserted_enumerable(enumerable) begin loop { result << yield(enum.next) } rescue StopIteration end result end def map_Enumerable_2(enumerable) result = [] index = 0 enum = asserted_enumerable(enumerable) begin loop do result << yield(index, enum.next) index = index +1 end rescue StopIteration end result end def asserted_enumerable(obj) unless enum = Puppet::Pops::Types::Enumeration.enumerator(obj) raise ArgumentError, ("#{self.class.name}(): wrong argument type (#{obj.class}; must be something enumerable.") end enum end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/functions/match.rb����������������������������������������������������������0000664�0052762�0001160�00000006560�12650174557�020475� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Returns the match result of matching a String or Array[String] with one of: # # * Regexp # * String - transformed to a Regexp # * Pattern type # * Regexp type # # Returns An Array with the entire match at index 0, and each subsequent submatch at index 1-n. # If there was no match, nil (ie. undef) is returned. If the value to match is an Array, a array # with mapped match results is returned. # # @example matching # "abc123".match(/([a-z]+)[1-9]+/) # => ["abc"] # "abc123".match(/([a-z]+)([1-9]+)/) # => ["abc", "123"] # # See the documentation for "The Puppet Type System" for more information about types. # @since 3.7.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 #{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_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-3.8.5/lib/puppet/functions/reduce.rb���������������������������������������������������������0000664�0052762�0001160�00000006156�12650174557�020651� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Applies a parameterized block to each element in a sequence of entries from the first # argument (_the enumerable_) and returns the last result of the invocation of the parameterized block. # # This function takes two mandatory arguments: the first should be an Array, Hash, or something of # enumerable type, and the last a parameterized block as produced by the puppet syntax: # # $a.reduce |$memo, $x| { ... } # reduce($a) |$memo, $x| { ... } # # When the first argument is an Array or someting of an enumerable type, the block is called with each entry in turn. # When the first argument is a hash each entry is converted to an array with `[key, value]` before being # fed to the block. An optional 'start memo' value may be supplied as an argument between the array/hash # and mandatory block. # # $a.reduce(start) |$memo, $x| { ... } # reduce($a, start) |$memo, $x| { ... } # # If no 'start memo' is given, the first invocation of the parameterized block will be given the first and second # elements of the enumeration, and if the enumerable has fewer than 2 elements, the first # element is produced as the result of the reduction without invocation of the block. # # On each subsequent invocation, the produced value of the invoked parameterized block is given as the memo in the # next invocation. # # @example Using reduce # # # Reduce an array # $a = [1,2,3] # $a.reduce |$memo, $entry| { $memo + $entry } # #=> 6 # # # Reduce hash values # $a = {a => 1, b => 2, c => 3} # $a.reduce |$memo, $entry| { [sum, $memo[1]+$entry[1]] } # #=> [sum, 6] # # # reverse a string # "abc".reduce |$memo, $char| { "$char$memo" } # #=>"cbe" # # It is possible to provide a starting 'memo' as an argument. # # @example Using reduce with given start 'memo' # # # Reduce an array # $a = [1,2,3] # $a.reduce(4) |$memo, $entry| { $memo + $entry } # #=> 10 # # # Reduce hash values # $a = {a => 1, b => 2, c => 3} # $a.reduce([na, 4]) |$memo, $entry| { [sum, $memo[1]+$entry[1]] } # #=> [sum, 10] # # @example Using reduce with an Integer range # # Integer[1,4].reduce |$memo, $x| { $memo + $x } # #=> 10 # # @since 3.2 for Array and Hash # @since 3.5 for additional enumerable types # @note requires `parser = future`. # Puppet::Functions.create_function(:reduce) do dispatch :reduce_without_memo do param 'Any', :enumerable block_param 'Callable[2,2]', :block end dispatch :reduce_with_memo do param 'Any', :enumerable param 'Any', :memo block_param 'Callable[2,2]', :block end def reduce_without_memo(enumerable) enum = asserted_enumerable(enumerable) enum.reduce {|memo, x| yield(memo, x) } end def reduce_with_memo(enumerable, given_memo) enum = asserted_enumerable(enumerable) enum.reduce(given_memo) {|memo, x| yield(memo, x) } end def asserted_enumerable(obj) unless enum = Puppet::Pops::Types::Enumeration.enumerator(obj) raise ArgumentError, ("#{self.class.name}(): wrong argument type (#{obj.class}; must be something enumerable.") end enum end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/functions/scanf.rb����������������������������������������������������������0000664�0052762�0001160�00000003313�12650174557�020464� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Scans a string and returns an array of one or more converted values as directed by a given format string.args # See the documenation 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 # the format String. A parameterized block may optionally be given, which is called with the result # that is produced by scanf if no block is present, the result of the block is then returned by # the function. # # The result of the scan is an Array, with each sucessfully scanned and transformed value.args The scanning # stops if a scan is unsuccesful and the scanned result up to that point is returned. If there was no # succesful scan at all, the result is an empty Array. The optional code block is typically used to # assert that the scan was succesful, and either produce the same input, or perform unwrapping of # the result # # @example scanning an integer in string form (result is an array with # integer, or empty if unsuccessful) # "42".scanf("%i") # # @example scanning and processing resulting array to assert result and unwrap # # "42".scanf("%i") |$x| { # unless $x[0] =~ Integer { # fail "Expected a well formed integer value, got '$x[0]'" # } # $x[0] # } # # @since 3.7.4 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-3.8.5/lib/puppet/functions/slice.rb����������������������������������������������������������0000664�0052762�0001160�00000010415�12650174557�020472� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Applies a parameterized block to each _slice_ of elements in a sequence of selected entries from the first # argument and returns the first argument, or if no block is given returns a new array with a concatenation of # the slices. # # This function takes two mandatory arguments: the first, `$a`, should be an Array, Hash, or something of # enumerable type (integer, Integer range, or String), and the second, `$n`, the number of elements to include # in each slice. The optional third argument should be a a parameterized block as produced by the puppet syntax: # # $a.slice($n) |$x| { ... } # slice($a) |$x| { ... } # # The parameterized block 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). # In case there are fewer remaining elements than the slice size for the last slice it will contain the remaining # elements. When the block has multiple parameters, excess parameters are set to undef for an array or # enumerable type, and to empty arrays for a Hash. # # $a.slice(2) |$first, $second| { ... } # # When the first argument is a Hash, each `key,value` entry is counted as one, e.g, a slice size of 2 will produce # an array of two arrays with key, and value. # # @example Using slice with Hash # # $a.slice(2) |$entry| { notice "first ${$entry[0]}, second ${$entry[1]}" } # $a.slice(2) |$first, $second| { notice "first ${first}, second ${second}" } # # When called without a block, the function produces a concatenated result of the slices. # # @example Using slice without a block # # 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]] # # @since 3.2 for Array and Hash # @since 3.5 for additional enumerable types # @note requires `parser = future`. # Puppet::Functions.create_function(:slice) do dispatch :slice_Hash do param 'Hash[Any, Any]', :hash param 'Integer[1, default]', :slize_size optional_block_param end dispatch :slice_Enumerable do param 'Any', :enumerable param 'Integer[1, default]', :slize_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 = asserted_enumerable(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}; "+ pblock.parameter_names.join(', ') end serving_size end def asserted_enumerable(obj) unless enum = Puppet::Pops::Types::Enumeration.enumerator(obj) raise ArgumentError, ("#{self.class.name}(): wrong argument type (#{obj.class}; must be something enumerable.") end enum end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/functions/with.rb�����������������������������������������������������������0000664�0052762�0001160�00000001045�12650174557�020345� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Call a lambda with the given arguments. Since the parameters of the lambda # are local to the lambda's scope, this can be used to create private sections # of logic in a class so that the variables are not visible outside of the # class. # # @example Using with # # # notices the array [1, 2, 'foo'] # with(1, 2, 'foo') |$x, $y, $z| { notice [$x, $y, $z] } # # @since 3.7.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-3.8.5/lib/puppet/graph.rb��������������������������������������������������������������������0000664�0052762�0001160�00000000537�12650174557�016470� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module 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-3.8.5/lib/puppet/graph/����������������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�016135� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/graph/key.rb����������������������������������������������������������������0000664�0052762�0001160�00000000715�12650174557�017256� 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-3.8.5/lib/puppet/graph/prioritizer.rb��������������������������������������������������������0000664�0052762�0001160�00000001027�12650174557�021045� 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-3.8.5/lib/puppet/graph/random_prioritizer.rb�������������������������������������������������0000664�0052762�0001160�00000000567�12650174557�022415� 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-3.8.5/lib/puppet/graph/rb_tree_map.rb��������������������������������������������������������0000664�0052762�0001160�00000023457�12650174557�020755� 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 http://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-3.8.5/lib/puppet/graph/relationship_graph.rb�������������������������������������������������0000664�0052762�0001160�00000016426�12650174557�022356� 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_autorequire_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 {} report_cycles_in_graph 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 |resource| overly_deferred_resource_handler.call(resource) finish(resource) 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_autorequire_dependencies(catalog) vertices.each do |vertex| vertex.autorequire(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 "Autorequiring #{edge.source}" add_edge(edge) 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) 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-3.8.5/lib/puppet/graph/sequential_prioritizer.rb���������������������������������������������0000664�0052762�0001160�00000001642�12650174557�023302� 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-3.8.5/lib/puppet/graph/simple_graph.rb�������������������������������������������������������0000664�0052762�0001160�00000042412�12650174557�021140� 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 # # 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.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) 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 def report_cycles_in_graph cycles = find_cycles_in_graph n = cycles.length # where is "pluralize"? --daniel 2011-01-22 return if n == 0 s = n == 1 ? '' : 's' message = "Found #{n} dependency cycle#{s}:\n" 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}." else message += "Try the '--graph' option and opening the " message += "resulting '.dot' file in OmniGraffle or GraphViz" end raise Puppet::Error, message 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 Puppet.settings.use(:graphing) 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") File.open(filename, "w") { |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? @upstream_from.clear @downstream_from.clear add_vertex(e.source) add_vertex(e.target) @in_to[ e.target][e.source] ||= []; @in_to[ e.target][e.source] |= [e] @out_from[e.source][e.target] ||= []; @out_from[e.source][e.target] |= [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 verticies. 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 # Call +dotty+ for the graph which is written to the file 'graph.dot' # in the # current directory. def dotty (params = {}, dotfile = 'graph.dot') File.open(dotfile, 'w') {|f| f << to_dot(params) } system('dotty', dotfile) end # Produce the graph files if requested. def write_graph(name) return unless Puppet[:graph] Puppet.settings.use(:graphing) file = File.join(Puppet[:graphdir], "#{name}.dot") File.open(file, "w") { |f| f.puts to_dot("name" => name.to_s.capitalize) } end # This flag may be set to true to use the new YAML serialzation # 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 # Stub class to allow graphs to be represented in YAML using the old # (version 2.6) format. class VertexWrapper attr_reader :vertex, :adjacencies def initialize(vertex, adjacencies) @vertex = vertex @adjacencies = adjacencies end def inspect { :@adjacencies => @adjacencies, :@vertex => @vertex.to_s }.inspect end end # instance_variable_get is used by Object.to_zaml to get instance # variables. Override it so that we can simulate the presence of # instance variables @edges and @vertices for serialization. def instance_variable_get(v) case v.to_s when '@edges' then edges when '@vertices' then if self.class.use_new_yaml_format vertices else result = {} vertices.each do |vertex| adjacencies = {} [:in, :out].each do |direction| adjacencies[direction] = {} adjacent(vertex, :direction => direction, :type => :edges).each do |edge| other_vertex = direction == :in ? edge.source : edge.target (adjacencies[direction][other_vertex] ||= Set.new).add(edge) end end result[vertex] = Puppet::Graph::SimpleGraph::VertexWrapper.new(vertex, adjacencies) end result end else super(v) end end def to_yaml_properties (super + [:@vertices, :@edges] - [:@in_to, :@out_from, :@upstream_from, :@downstream_from]).uniq end def yaml_initialize(tag, var) initialize() vertices = var.delete('vertices') edges = var.delete('edges') if vertices.is_a?(Hash) # Support old (2.6) format vertices = vertices.keys end vertices.each { |v| add_vertex(v) } edges.each { |e| add_edge(e) } var.each do |varname, value| instance_variable_set("@#{varname}", value) end 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-3.8.5/lib/puppet/graph/title_hash_prioritizer.rb���������������������������������������������0000664�0052762�0001160�00000001036�12650174557�023251� 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-3.8.5/lib/puppet/indirector.rb���������������������������������������������������������������0000664�0052762�0001160�00000005016�12650174557�017526� 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" 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 #{@indirection.name}; cannot also handle #{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-3.8.5/lib/puppet/indirector/�����������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017176� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/active_record.rb�������������������������������������������������0000664�0052762�0001160�00000001016�12650174557�022333� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/rails' require 'puppet/indirector' class Puppet::Indirector::ActiveRecord < Puppet::Indirector::Terminus class << self attr_accessor :ar_model end def self.use_ar_model(klass) self.ar_model = klass end def ar_model self.class.ar_model end def initialize Puppet::Rails.init end def find(request) return nil unless instance = ar_model.find_by_name(request.key) instance.to_puppet end def save(request) ar_model.from_puppet(request.instance).save end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/catalog/���������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020610� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/catalog/active_record.rb�����������������������������������������0000664�0052762�0001160�00000002306�12650174557�023750� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/rails/host' require 'puppet/indirector/active_record' require 'puppet/resource/catalog' class Puppet::Resource::Catalog::ActiveRecord < Puppet::Indirector::ActiveRecord use_ar_model Puppet::Rails::Host desc "A component of ActiveRecord storeconfigs. ActiveRecord-based storeconfigs and inventory are deprecated. See http://links.puppetlabs.com/activerecord-deprecation" def initialize Puppet.deprecation_warning "ActiveRecord-based storeconfigs and inventory are deprecated. See http://links.puppetlabs.com/activerecord-deprecation" super end # We don't retrieve catalogs from storeconfigs def find(request) nil end # Save the values from a Facts instance as the facts on a Rails Host instance. def save(request) catalog = request.instance host = ar_model.find_by_name(catalog.name) || ar_model.create(:name => catalog.name) host.railsmark "Saved catalog to database" do host.merge_resources(catalog.vertices) host.last_compile = Time.now if node = Puppet::Node.indirection.find(catalog.name) host.ip = node.parameters["ipaddress"] host.environment = node.environment.to_s end host.save end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/catalog/compiler.rb����������������������������������������������0000664�0052762�0001160�00000013133�12650174557�022751� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node' require 'puppet/resource/catalog' require 'puppet/indirector/code' require 'puppet/util/profiler' require 'yaml' class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code desc "Compiles catalogs on demand using Puppet's compiler." include Puppet::Util attr_accessor :code 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.key}" end Puppet::Util::Profiler.profile("Found facts", [:compiler, :find_facts]) do # If the facts were encoded as yaml, then the param reconstitution system # in Network::HTTP::Handler will automagically deserialize the value. if text_facts.is_a?(Puppet::Node::Facts) facts = text_facts else # We unescape here because the corresponding code in Puppet::Configurer::FactHandler escapes facts = Puppet::Node::Facts.convert_from(format, CGI.unescape(text_facts)) end unless facts.name == request.key raise Puppet::Error, "Catalog for #{request.key.inspect} was requested with fact definition for the wrong node (#{facts.name.inspect})." end facts.add_timestamp options = { :environment => request.environment, :transaction_uuid => request.options[:transaction_uuid], } Puppet::Node::Facts.indirection.save(facts, nil, options) end end # Compile a node's catalog. def find(request) extract_facts_from_request(request) node = node_from_request(request) node.trusted_data = Puppet.lookup(:trusted_information) { Puppet::Context::TrustedInformation.local(node) }.to_h if catalog = compile(node) return catalog else # This shouldn't actually happen; we should either return # a config or raise an exception. return nil 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 # 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.merge(@server_facts) end # Compile the actual catalog. def compile(node) str = "Compiled catalog for #{node.name}" str += " in environment #{node.environment}" if node.environment config = nil benchmark(:notice, str) do Puppet::Util::Profiler.profile(str, [:compiler, :compile, node.environment, node.name]) do begin config = Puppet::Parser::Compiler.compile(node) rescue Puppet::Error => detail Puppet.err(detail.to_s) if networked? raise end end end config end # Turn our host name into a node object. def find_node(name, environment, transaction_uuid) 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) rescue => detail message = "Failed when searching for node #{name}: #{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(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]) return node end raise ArgumentError, "Could not find node '#{name}'; cannot compile" 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}" 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-3.8.5/lib/puppet/indirector/catalog/json.rb��������������������������������������������������0000664�0052762�0001160�00000000307�12650174557�022107� 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." end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/catalog/msgpack.rb�����������������������������������������������0000664�0052762�0001160�00000000327�12650174557�022565� 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-3.8.5/lib/puppet/indirector/catalog/queue.rb�������������������������������������������������0000664�0052762�0001160�00000000521�12650174557�022260� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/resource/catalog' require 'puppet/indirector/queue' class Puppet::Resource::Catalog::Queue < Puppet::Indirector::Queue desc "Part of async storeconfigs, requiring the puppet queue daemon. ActiveRecord-based storeconfigs and inventory are deprecated. See http://links.puppetlabs.com/activerecord-deprecation" end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/catalog/rest.rb��������������������������������������������������0000664�0052762�0001160�00000000275�12650174557�022117� 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-3.8.5/lib/puppet/indirector/catalog/static_compiler.rb���������������������������������������0000664�0052762�0001160�00000020600�12650174557�024315� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node' require 'puppet/resource/catalog' require 'puppet/indirector/catalog/compiler' class Puppet::Resource::Catalog::StaticCompiler < Puppet::Resource::Catalog::Compiler desc %q{Compiles catalogs on demand using the optional static compiler. This functions similarly to the normal compiler, but it replaces puppet:/// file URLs with explicit metadata and file content hashes, expecting puppet agent to fetch the exact specified content from the filebucket. This guarantees that a given catalog will always result in the same file states. It also decreases catalog application time and fileserver load, at the cost of increased compilation time. This terminus works today, but cannot be used without additional configuration. Specifically: * You must create a special filebucket resource --- with the title `puppet` and the `path` attribute set to `false` --- in site.pp or somewhere else where it will be added to every node's catalog. Using `puppet` as the title is mandatory; the static compiler treats this title as magical. filebucket { puppet: path => false, } * You must set `catalog_terminus = static_compiler` in the puppet master's puppet.conf. * The puppet master's auth.conf must allow authenticated nodes to access the `file_bucket_file` endpoint. This is enabled by default (see the `path /file` rule), but if you have made your auth.conf more restrictive, you may need to re-enable it.) * If you are using multiple puppet masters, you must configure load balancer affinity for agent nodes. This is because puppet masters other than the one that compiled a given catalog may not have stored the required file contents in their filebuckets.} def find(request) return nil unless catalog = super raise "Did not get catalog back" unless catalog.is_a?(model) catalog.resources.find_all { |res| res.type == "File" }.each do |resource| next unless source = resource[:source] next unless source =~ /^puppet:/ file = resource.to_ral if file.recurse? add_children(request.key, catalog, resource, file) else find_and_replace_metadata(request.key, resource, file) end end catalog end # Take a resource with a fileserver based file source remove the source # parameter, and insert the file metadata into the resource. # # This method acts to do the fileserver metadata retrieval in advance, while # the file source is local and doesn't require an HTTP request. It retrieves # the file metadata for a given file resource, removes the source parameter # from the resource, inserts the metadata into the file resource, and uploads # the file contents of the source to the file bucket. # # @param host [String] The host name of the node requesting this catalog # @param resource [Puppet::Resource] The resource to replace the metadata in # @param file [Puppet::Type::File] The file RAL associated with the resource def find_and_replace_metadata(host, resource, file) # We remove URL info from it, so it forces a local copy # rather than routing through the network. # Weird, but true. newsource = file[:source][0].sub("puppet:///", "") file[:source][0] = newsource raise "Could not get metadata for #{resource[:source]}" unless metadata = file.parameter(:source).metadata replace_metadata(host, resource, metadata) end # Rewrite a given file resource with the metadata from a fileserver based file # # This performs the actual metadata rewrite for the given file resource and # uploads the content of the source file to the filebucket. # # @param host [String] The host name of the node requesting this catalog # @param resource [Puppet::Resource] The resource to add the metadata to # @param metadata [Puppet::FileServing::Metadata] The metadata of the given fileserver based file def replace_metadata(host, resource, metadata) [:mode, :owner, :group].each do |param| resource[param] ||= metadata.send(param) end resource[:ensure] = metadata.ftype if metadata.ftype == "file" unless resource[:content] resource[:content] = metadata.checksum resource[:checksum] = metadata.checksum_type end end store_content(resource) if resource[:ensure] == "file" old_source = resource.delete(:source) Puppet.info "Metadata for #{resource} in catalog for '#{host}' added from '#{old_source}'" end # Generate children resources for a recursive file and add them to the catalog. # # @param host [String] The host name of the node requesting this catalog # @param catalog [Puppet::Resource::Catalog] # @param resource [Puppet::Resource] # @param file [Puppet::Type::File] The file RAL associated with the resource def add_children(host, catalog, resource, file) file = resource.to_ral children = get_child_resources(host, catalog, resource, file) remove_existing_resources(children, catalog) children.each do |name, res| catalog.add_resource res catalog.add_edge(resource, res) end end # Given a recursive file resource, recursively generate its children resources # # @param host [String] The host name of the node requesting this catalog # @param catalog [Puppet::Resource::Catalog] # @param resource [Puppet::Resource] # @param file [Puppet::Type::File] The file RAL associated with the resource # # @return [Array<Puppet::Resource>] The recursively generated File resources for the given resource def get_child_resources(host, catalog, resource, file) sourceselect = file[:sourceselect] children = {} source = resource[:source] # This is largely a copy of recurse_remote in File total = file[:source].collect do |source| next unless result = file.perform_recursion(source) return if top = result.find { |r| r.relative_path == "." } and top.ftype != "directory" result.each { |data| data.source = "#{source}/#{data.relative_path}" } 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 found.include?(data.relative_path) result end end total.each do |meta| # This is the top-level parent directory if meta.relative_path == "." replace_metadata(host, resource, meta) next end children[meta.relative_path] ||= Puppet::Resource.new(:file, File.join(file[:path], meta.relative_path)) # I think this is safe since it's a URL, not an actual file children[meta.relative_path][:source] = source + "/" + meta.relative_path resource.each do |param, value| # These should never be passed to our children. unless [:parent, :ensure, :recurse, :recurselimit, :target, :alias, :source].include? param children[meta.relative_path][param] = value end end replace_metadata(host, children[meta.relative_path], meta) end children end # Remove any file resources in the catalog that will be duplicated by the # given file resources. # # @param children [Array<Puppet::Resource>] # @param catalog [Puppet::Resource::Catalog] def remove_existing_resources(children, catalog) existing_names = catalog.resources.collect { |r| r.to_s } both = (existing_names & children.keys).inject({}) { |hash, name| hash[name] = true; hash } both.each { |name| children.delete(name) } end # Retrieve the source of a file resource using a fileserver based source and # upload it to the filebucket. # # @param resource [Puppet::Resource] def store_content(resource) @summer ||= Object.new @summer.extend(Puppet::Util::Checksums) type = @summer.sumtype(resource[:content]) sum = @summer.sumdata(resource[:content]) if Puppet::FileBucket::File.indirection.find("#{type}/#{sum}") Puppet.info "Content for '#{resource[:source]}' already exists" else Puppet.info "Storing content for source '#{resource[:source]}'" content = Puppet::FileServing::Content.indirection.find(resource[:source]) file = Puppet::FileBucket::File.new(content.content) Puppet::FileBucket::File.indirection.save(file) end end end ��������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/catalog/store_configs.rb�����������������������������������������0000664�0052762�0001160�00000000374�12650174557�024006� 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-3.8.5/lib/puppet/indirector/catalog/yaml.rb��������������������������������������������������0000664�0052762�0001160�00000001026�12650174557�022077� 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-3.8.5/lib/puppet/indirector/certificate/�����������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�021460� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/certificate/ca.rb������������������������������������������������0000664�0052762�0001160�00000000377�12650174557�022400� 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-3.8.5/lib/puppet/indirector/certificate/disabled_ca.rb���������������������������������������0000664�0052762�0001160�00000001245�12650174557�024222� 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-3.8.5/lib/puppet/indirector/certificate/file.rb����������������������������������������������0000664�0052762�0001160�00000000350�12650174557�022723� 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-3.8.5/lib/puppet/indirector/certificate/rest.rb����������������������������������������������0000664�0052762�0001160�00000000627�12650174557�022770� 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-3.8.5/lib/puppet/indirector/certificate_request/���������������������������������������������0000775�0052762�0001160�00000000000�12650174565�023230� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/certificate_request/ca.rb����������������������������������������0000664�0052762�0001160�00000001313�12650174557�024137� 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.key} already has a #{host.state} certificate; new certificate will overwrite it" else raise "#{request.key} already has a #{host.state} certificate; ignoring certificate request" end end result = super Puppet.notice "#{request.key} has a waiting certificate request" result end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/certificate_request/disabled_ca.rb�������������������������������0000664�0052762�0001160�00000001302�12650174557�025764� 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-3.8.5/lib/puppet/indirector/certificate_request/file.rb��������������������������������������0000664�0052762�0001160�00000000365�12650174557�024501� 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-3.8.5/lib/puppet/indirector/certificate_request/memory.rb������������������������������������0000664�0052762�0001160�00000000354�12650174557�025070� 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-3.8.5/lib/puppet/indirector/certificate_request/rest.rb��������������������������������������0000664�0052762�0001160�00000000454�12650174557�024536� 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-3.8.5/lib/puppet/indirector/certificate_revocation_list/�������������������������������������0000775�0052762�0001160�00000000000�12650174565�024744� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/certificate_revocation_list/ca.rb��������������������������������0000664�0052762�0001160�00000000400�12650174557�025647� 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-3.8.5/lib/puppet/indirector/certificate_revocation_list/disabled_ca.rb�����������������������0000664�0052762�0001160�00000001330�12650174557�027501� 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-3.8.5/lib/puppet/indirector/certificate_revocation_list/file.rb������������������������������0000664�0052762�0001160�00000000371�12650174557�026212� 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-3.8.5/lib/puppet/indirector/certificate_revocation_list/rest.rb������������������������������0000664�0052762�0001160�00000000503�12650174557�026245� 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) end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/certificate_status.rb��������������������������������������������0000664�0052762�0001160�00000000115�12650174557�023406� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector' class Puppet::Indirector::CertificateStatus end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/certificate_status/����������������������������������������������0000775�0052762�0001160�00000000000�12650174565�023063� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/certificate_status/file.rb���������������������������������������0000664�0052762�0001160�00000005553�12650174557�024340� 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.key}: #{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.key} without a certificate request" 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.key} because has it doesn't have a signed certificate" unless certificate ca.revoke(request.key) else raise Puppet::Error, "State #{request.instance.desired_state} invalid; Must specify desired state of 'signed' or 'revoked' for host #{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-3.8.5/lib/puppet/indirector/certificate_status/rest.rb���������������������������������������0000664�0052762�0001160�00000000555�12650174557�024373� 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-3.8.5/lib/puppet/indirector/code.rb����������������������������������������������������������0000664�0052762�0001160�00000000261�12650174557�020435� 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-3.8.5/lib/puppet/indirector/couch.rb���������������������������������������������������������0000664�0052762�0001160�00000003632�12650174557�020631� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Indirector::Couch < Puppet::Indirector::Terminus # The CouchRest database instance. One database instance per Puppet runtime # should be sufficient. # def self.db; @db ||= CouchRest.database! Puppet[:couchdb_url] end def db; self.class.db end def find(request) attributes_of get(request) end def initialize(*args) raise "Couch terminus not supported without couchrest gem" unless Puppet.features.couchdb? super end # Create or update the couchdb document with the request's data hash. # def save(request) raise ArgumentError, "PUT does not accept options" unless request.options.empty? update(request) || create(request) end private # RKH:TODO: Do not depend on error handling, check if the document exists # first. (Does couchrest support this?) # def get(request) db.get(id_for(request)) rescue RestClient::ResourceNotFound Puppet.debug "No couchdb document with id: #{id_for(request)}" return nil end def update(request) doc = get request return unless doc doc.merge!(hash_from(request)) doc.save true end def create(request) db.save_doc hash_from(request) end # The attributes hash that is serialized to CouchDB as JSON. It includes # metadata that is used to help aggregate data in couchdb. Add # model-specific attributes in subclasses. # def hash_from(request) { "_id" => id_for(request), "puppet_type" => document_type_for(request) } end # The couchdb response stripped of metadata, used to instantiate the model # instance that is returned by save. # def attributes_of(response) response && response.reject{|k,v| k =~ /^(_rev|puppet_)/ } end def document_type_for(request) request.indirection_name end # The id used to store the object in couchdb. Implemented in subclasses. # def id_for(request) raise NotImplementedError end end ������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/data_binding/����������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�021601� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/data_binding/hiera.rb��������������������������������������������0000664�0052762�0001160�00000000237�12650174557�023221� 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-3.8.5/lib/puppet/indirector/data_binding/none.rb���������������������������������������������0000664�0052762�0001160�00000000245�12650174557�023067� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/none' class Puppet::DataBinding::None < Puppet::Indirector::None desc "A Dummy terminus that always returns nil for data lookups." end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/direct_file_server.rb��������������������������������������������0000664�0052762�0001160�00000001047�12650174557�023365� 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) instance = model.new(request.key) instance.links = request.options[:links] if request.options[:links] instance end def search(request) return nil unless Puppet::FileSystem.exist?(request.key) path2instances(request, request.key) end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/envelope.rb������������������������������������������������������0000664�0052762�0001160�00000000355�12650174557�021344� 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-3.8.5/lib/puppet/indirector/errors.rb��������������������������������������������������������0000664�0052762�0001160�00000000143�12650174557�021036� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/error' module Puppet::Indirector class ValidationError < Puppet::Error; end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/exec.rb����������������������������������������������������������0000664�0052762�0001160�00000002233�12650174557�020450� 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}", 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-3.8.5/lib/puppet/indirector/face.rb����������������������������������������������������������0000664�0052762�0001160�00000011105�12650174557�020420� 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 result = indirection.__send__(method, key, options) rescue => detail message = "Could not call '#{method}' on '#{indirection_name}': #{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 {|key, options| call_indirection_method :find, key, options[:extra] } 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 '#{Puppet.run_mode.name}': #{t}" else "No default terminus class for run 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_name}" end @indirection end def set_terminus(from) begin indirection.terminus_class = from rescue => detail msg = "Could not set '#{indirection.name}' terminus to '#{from}' (#{detail}); valid terminus types are #{self.class.terminus_classes(indirection.name).join(", ") }" raise detail, msg, detail.backtrace end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/facts/�����������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020276� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/facts/active_record.rb�������������������������������������������0000664�0052762�0001160�00000002642�12650174557�023441� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/rails/fact_name' require 'puppet/rails/fact_value' require 'puppet/rails/host' require 'puppet/indirector/active_record' class Puppet::Node::Facts::ActiveRecord < Puppet::Indirector::ActiveRecord use_ar_model Puppet::Rails::Host desc "A component of ActiveRecord storeconfigs and inventory. ActiveRecord-based storeconfigs and inventory are deprecated. See http://links.puppetlabs.com/activerecord-deprecation" def initialize Puppet.deprecation_warning "ActiveRecord-based storeconfigs and inventory are deprecated. See http://links.puppetlabs.com/activerecord-deprecation" super end # Find the Rails host and pull its facts as a Facts instance. def find(request) return nil unless host = ar_model.find_by_name(request.key, :include => {:fact_values => :fact_name}) facts = Puppet::Node::Facts.new(host.name) facts.values = host.get_facts_hash.inject({}) do |hash, ary| # Convert all single-member arrays into plain values. param = ary[0] values = ary[1].collect { |v| v.value } values = values[0] if values.length == 1 hash[param] = values hash end facts end # Save the values from a Facts instance as the facts on a Rails Host instance. def save(request) facts = request.instance host = ar_model.find_by_name(facts.name) || ar_model.create(:name => facts.name) host.merge_facts(facts.values) host.save end end ����������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/facts/couch.rb���������������������������������������������������0000664�0052762�0001160�00000001676�12650174557�021737� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node/facts' require 'puppet/indirector/couch' class Puppet::Node::Facts::Couch < Puppet::Indirector::Couch desc "DEPRECATED. This terminus will be removed in Puppet 4.0. Store facts in CouchDB. This should not be used with the inventory service; it is for more obscure custom integrations. If you are wondering whether you should use it, you shouldn't; use PuppetDB instead." # Return the facts object or nil if there is no document def find(request) doc = super doc ? model.new(doc['_id'], doc['facts']) : nil end private # Facts values are stored to the document's 'facts' attribute. Hostname is # stored to 'name' # def hash_from(request) super.merge('facts' => request.instance.values) end # Facts are stored to the 'node' document. def document_type_for(request) 'node' end # The id used to store the object in couchdb. def id_for(request) request.key.to_s end end ������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/facts/facter.rb��������������������������������������������������0000664�0052762�0001160�00000004767�12650174557�022106� 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 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 self.class.setup_external_search_paths(request) if Puppet.features.external_facts? self.class.setup_search_paths(request) result = Puppet::Node::Facts.new(request.key, Facter.to_hash) result.add_local_facts Puppet[:stringify_facts] ? result.stringify : result.sanitize result end private 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-3.8.5/lib/puppet/indirector/facts/inventory_active_record.rb���������������������������������0000664�0052762�0001160�00000010156�12650174557�025555� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/rails' require 'puppet/rails/inventory_node' require 'puppet/rails/inventory_fact' require 'puppet/indirector/active_record' require 'puppet/util/retryaction' class Puppet::Node::Facts::InventoryActiveRecord < Puppet::Indirector::ActiveRecord desc "Medium-performance fact storage suitable for the inventory service. Most users should use PuppetDB instead. Note: ActiveRecord-based storeconfigs and inventory are deprecated. See http://links.puppetlabs.com/activerecord-deprecation" def initialize raise Puppet::Error, "ActiveRecords-based inventory is unsupported with Ruby 2 and Rails 3.0" if RUBY_VERSION[0] == '2' Puppet.deprecation_warning "ActiveRecord-based storeconfigs and inventory are deprecated. See http://links.puppetlabs.com/activerecord-deprecation" super end def find(request) node = Puppet::Rails::InventoryNode.find_by_name(request.key) return nil unless node facts = Puppet::Node::Facts.new(node.name, node.facts_to_hash) facts.timestamp = node.timestamp facts end def save(request) Puppet::Util::RetryAction.retry_action :retries => 4, :retry_exceptions => {ActiveRecord::StatementInvalid => 'MySQL Error. Retrying'} do facts = request.instance node = Puppet::Rails::InventoryNode.find_by_name(request.key) || Puppet::Rails::InventoryNode.create(:name => request.key, :timestamp => facts.timestamp) node.timestamp = facts.timestamp ActiveRecord::Base.transaction do Puppet::Rails::InventoryFact.delete_all(:node_id => node.id) # We don't want to save internal values as facts, because those are # metadata that belong on the node facts.values.each do |name,value| next if name.to_s =~ /^_/ node.facts.build(:name => name, :value => value) end node.save end end end def search(request) return [] unless request.options matching_nodes = [] fact_filters = Hash.new {|h,k| h[k] = []} meta_filters = Hash.new {|h,k| h[k] = []} request.options.each do |key,value| type, name, operator = key.to_s.split(".") operator ||= "eq" if type == "facts" fact_filters[operator] << [name,value] elsif type == "meta" and name == "timestamp" meta_filters[operator] << [name,value] end end matching_nodes = nodes_matching_fact_filters(fact_filters) + nodes_matching_meta_filters(meta_filters) # to_a because [].inject == nil matching_nodes.inject {|nodes,this_set| nodes & this_set}.to_a.sort end private def nodes_matching_fact_filters(fact_filters) node_sets = [] fact_filters['eq'].each do |name,value| node_sets << Puppet::Rails::InventoryNode.has_fact_with_value(name,value).map {|node| node.name} end fact_filters['ne'].each do |name,value| node_sets << Puppet::Rails::InventoryNode.has_fact_without_value(name,value).map {|node| node.name} end { 'gt' => '>', 'lt' => '<', 'ge' => '>=', 'le' => '<=' }.each do |operator_name,operator| fact_filters[operator_name].each do |name,value| facts = Puppet::Rails::InventoryFact.find_by_sql(["SELECT inventory_facts.value, inventory_nodes.name AS node_name FROM inventory_facts INNER JOIN inventory_nodes ON inventory_facts.node_id = inventory_nodes.id WHERE inventory_facts.name = ?", name]) node_sets << facts.select {|fact| fact.value.to_f.send(operator, value.to_f)}.map {|fact| fact.node_name} end end node_sets end def nodes_matching_meta_filters(meta_filters) node_sets = [] { 'eq' => '=', 'ne' => '!=', 'gt' => '>', 'lt' => '<', 'ge' => '>=', 'le' => '<=' }.each do |operator_name,operator| meta_filters[operator_name].each do |name,value| node_sets << Puppet::Rails::InventoryNode.find(:all, :select => "name", :conditions => ["timestamp #{operator} ?", value]).map {|node| node.name} end end node_sets end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/facts/inventory_service.rb���������������������������������������0000664�0052762�0001160�00000001134�12650174557�024400� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node/facts' require 'puppet/indirector/rest' class Puppet::Node::Facts::InventoryService < Puppet::Indirector::REST desc "Find and save facts about nodes using a remote inventory service." use_server_setting(:inventory_server) use_port_setting(:inventory_port) # We don't want failing to upload to the inventory service to cause any # failures, so we just suppress them and warn. def save(request) begin super true rescue => e Puppet.warning "Could not upload facts for #{request.key} to inventory service: #{e.to_s}" false end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/facts/memory.rb��������������������������������������������������0000664�0052762�0001160�00000000546�12650174557�022141� 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-3.8.5/lib/puppet/indirector/facts/network_device.rb������������������������������������������0000664�0052762�0001160�00000001404�12650174557�023633� 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." # 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 Puppet[:stringify_facts] ? result.stringify : 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-3.8.5/lib/puppet/indirector/facts/rest.rb����������������������������������������������������0000664�0052762�0001160�00000000406�12650174557�021601� 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." use_server_setting(:inventory_server) use_port_setting(:inventory_port) end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/facts/store_configs.rb�������������������������������������������0000664�0052762�0001160�00000000360�12650174557�023467� 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.} end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/facts/yaml.rb����������������������������������������������������0000664�0052762�0001160�00000003567�12650174557�021601� 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-3.8.5/lib/puppet/indirector/file_bucket_file/������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�022451� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/file_bucket_file/file.rb�����������������������������������������0000664�0052762�0001160�00000012371�12650174557�023722� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/code' require 'puppet/file_bucket/file' require 'puppet/util/checksums' require 'fileutils' module Puppet::FileBucketFile class File < Puppet::Indirector::Code include Puppet::Util::Checksums desc "Store files in a directory set based on their checksums." def find(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') 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 #{request.options[:diff_with]}" unless Puppet::FileSystem.exist?(other_contents_file) return `diff #{Puppet::FileSystem.path_string(contents_file).inspect} #{Puppet::FileSystem.path_string(other_contents_file).inspect}` else Puppet.info "FileBucket read #{checksum}" model.new(Puppet::FileSystem.binread(contents_file)) end else nil end 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::FileSystem.open(paths_file, 0640, 'a+') 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 contents_file [Object] Opaque file path # @param paths_file [Object] Opaque file path # 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::FileSystem.exclusive_open(paths_file, 0640, 'a+') do |f| if Puppet::FileSystem.exist?(contents_file) verify_identical_file!(contents_file, bucket_file) Puppet::FileSystem.touch(contents_file) else Puppet::FileSystem.open(contents_file, 0440, 'wb') do |of| # PUP-1044 writes all of the contents bucket_file.stream() do |src| FileUtils.copy_stream(src, of) end end 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.inspect}" if checksum_type != Puppet[:digest_algorithm] expected = method(checksum_type + "_hex_length").call raise "Invalid 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 [Object] Opaque file path # @param bucket_file [IO] def verify_identical_file!(contents_file, bucket_file) if bucket_file.size == Puppet::FileSystem.size(contents_file) if bucket_file.stream() {|s| Puppet::FileSystem.compare_stream(contents_file, s) } Puppet.info "FileBucket got a duplicate file #{bucket_file.checksum}" return end end # If the contents or sizes don't match, then we've found a conflict. # Unlikely, but quite bad. raise Puppet::FileBucket::BucketError, "Got passed new contents for sum #{bucket_file.checksum}" end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/file_bucket_file/rest.rb�����������������������������������������0000664�0052762�0001160�00000000353�12650174557�023755� 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-3.8.5/lib/puppet/indirector/file_bucket_file/selector.rb�������������������������������������0000664�0052762�0001160�00000002002�12650174557�024611� 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-3.8.5/lib/puppet/indirector/file_content.rb��������������������������������������������������0000664�0052762�0001160�00000000202�12650174557�022167� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A stub class, so our constants work. class Puppet::Indirector::FileContent # :nodoc: end require 'puppet/file_serving/content' ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/file_content/����������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�021647� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/file_content/file.rb���������������������������������������������0000664�0052762�0001160�00000000401�12650174557�023107� 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-3.8.5/lib/puppet/indirector/file_content/file_server.rb��������������������������������������0000664�0052762�0001160�00000000412�12650174557�024477� 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-3.8.5/lib/puppet/indirector/file_content/rest.rb���������������������������������������������0000664�0052762�0001160�00000000427�12650174557�023155� 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-3.8.5/lib/puppet/indirector/file_content/selector.rb�����������������������������������������0000664�0052762�0001160�00000001332�12650174557�024014� 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-3.8.5/lib/puppet/indirector/file_metadata.rb�������������������������������������������������0000664�0052762�0001160�00000000204�12650174557�022277� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A stub class, so our constants work. class Puppet::Indirector::FileMetadata # :nodoc: end require 'puppet/file_serving/metadata' ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/file_metadata/���������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�021755� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/file_metadata/file.rb��������������������������������������������0000664�0052762�0001160�00000001023�12650174557�023216� 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." def find(request) return unless data = super data.collect(request.options[:source_permissions]) data end def search(request) return unless result = super result.each { |instance| instance.collect } result end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/file_metadata/file_server.rb�������������������������������������0000664�0052762�0001160�00000000415�12650174557�024610� 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-3.8.5/lib/puppet/indirector/file_metadata/rest.rb��������������������������������������������0000664�0052762�0001160�00000000432�12650174557�023257� 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-3.8.5/lib/puppet/indirector/file_metadata/selector.rb����������������������������������������0000664�0052762�0001160�00000001335�12650174557�024125� 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-3.8.5/lib/puppet/indirector/file_server.rb���������������������������������������������������0000664�0052762�0001160�00000004023�12650174557�022030� 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, file_path = configuration.split_path(request) # If we're not serving this mount, then access is denied. return false unless mount mount.allowed?(request.node, request.ip) 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) result = model.new(path) result.links = request.options[:links] if request.options[:links] result.collect(request.options[:source_permissions]) result 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.key}' in environment #{request.environment}" return nil end 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| inst = model.new(base_path, :relative_path => file) inst.links = request.options[:links] if request.options[:links] inst.collect inst end end private # Our fileserver configuration, if needed. def configuration Puppet::FileServing::Configuration.configuration end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/hiera.rb���������������������������������������������������������0000664�0052762�0001160�00000002152�12650174557�020614� 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? 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) hiera.lookup(request.key, nil, Hiera::Scope.new(request.options[:variables]), nil, nil) rescue *DataBindingExceptions => detail raise Puppet::DataBinding::LookupError.new(detail.message, detail) end private 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" end config[:logger] = 'puppet' config end def self.hiera @hiera ||= Hiera.new(:config => hiera_config) end def hiera self.class.hiera end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/indirection.rb���������������������������������������������������0000664�0052762�0001160�00000025021�12650174557�022033� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/docs' require 'puppet/util/profiler' require 'puppet/util/methodhelper' require 'puppet/indirector/envelope' require 'puppet/indirector/request' require 'puppet/util/instrumentation/instrumentable' # 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 extend Puppet::Util::Instrumentation::Instrumentable attr_accessor :name, :model attr_reader :termini probe :find, :label => Proc.new { |parent, key, *args| "find_#{parent.name}_#{parent.terminus_class}" }, :data => Proc.new { |parent, key, *args| { :key => key }} probe :save, :label => Proc.new { |parent, key, *args| "save_#{parent.name}_#{parent.terminus_class}" }, :data => Proc.new { |parent, key, *args| { :key => key }} probe :search, :label => Proc.new { |parent, key, *args| "search_#{parent.name}_#{parent.terminus_class}" }, :data => Proc.new { |parent, key, *args| { :key => key }} probe :destroy, :label => Proc.new { |parent, key, *args| "destroy_#{parent.name}_#{parent.terminus_class}" }, :data => Proc.new { |parent, key, *args| { :key => key }} @@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) raise ArgumentError, "Indirection TTL must be an integer" unless value.is_a?(Fixnum) @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") 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 #{self.name}; cannot redirect" 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 #{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) raise ArgumentError, "Invalid terminus name #{terminus_class.inspect}" unless terminus_class and terminus_class.to_s != "" unless Puppet::Indirector::Terminus.terminus_class(self.name, terminus_class) raise ArgumentError, "Could not find terminus #{terminus_class} for indirection #{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? return nil unless instance = cache.find(request(:find, key, nil, options)) Puppet.info "Expiring the #{self.name} cache of #{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? Puppet.info "Caching #{self.name} for #{request.key}" cache.save request(:save, key, result, options) end filtered = result if terminus.respond_to?(:filter) Puppet::Util::Profiler.profile("Filtered result for #{self.name} #{request.key}", [:indirector, :filter, self.name, request.key]) do filtered = terminus.filter(result) 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 #{self.name} for #{request.key} from cache; expired at #{cached.expiration}" return nil end Puppet.debug "Using cached #{self.name} for #{request.key}" cached rescue => detail Puppet.log_exception(detail, "Cached #{self.name} for #{request.key} failed: #{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" 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? 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 = "Not authorized to call #{request.method} on #{request}" msg += " with #{request.options.inspect}" unless request.options.empty? raise ArgumentError, msg end end # Setup a request, pick the appropriate terminus, check the request's authorization, and return it. def prepare(request) # Pick our terminus. if respond_to?(:select_terminus) unless terminus_name = select_terminus(request) raise ArgumentError, "Could not determine appropriate terminus for #{request}" end else terminus_name = terminus_class end 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 #{self.name}" end klass.new end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/instrumentation_data.rb������������������������������������������0000664�0052762�0001160�00000000131�12650174557�023753� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A stub class, so our constants work. class Puppet::Indirector::InstrumentationData end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/instrumentation_data/��������������������������������������������0000775�0052762�0001160�00000000000�12650174565�023432� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/instrumentation_data/local.rb������������������������������������0000664�0052762�0001160�00000000773�12650174557�025061� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/instrumentation_data' class Puppet::Indirector::InstrumentationData::Local < Puppet::Indirector::Code desc "Undocumented." def find(request) model.new(request.key) end def search(request) raise Puppet::DevError, "You cannot search for instrumentation data" end def save(request) raise Puppet::DevError, "You cannot save instrumentation data" end def destroy(request) raise Puppet::DevError, "You cannot remove instrumentation data" end end �����puppet-3.8.5/lib/puppet/indirector/instrumentation_data/rest.rb�������������������������������������0000664�0052762�0001160�00000000277�12650174557�024743� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/rest' require 'puppet/indirector/instrumentation_data' class Puppet::Indirector::InstrumentationData::Rest < Puppet::Indirector::REST desc "Undocumented." end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/instrumentation_listener.rb��������������������������������������0000664�0052762�0001160�00000000135�12650174557�024673� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A stub class, so our constants work. class Puppet::Indirector::InstrumentationListener end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/instrumentation_listener/����������������������������������������0000775�0052762�0001160�00000000000�12650174565�024346� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/instrumentation_listener/local.rb��������������������������������0000664�0052762�0001160�00000001236�12650174557�025770� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/instrumentation_listener' class Puppet::Indirector::InstrumentationListener::Local < Puppet::Indirector::Code desc "Undocumented." def find(request) Puppet::Util::Instrumentation[request.key] end def search(request) Puppet::Util::Instrumentation.listeners end def save(request) res = request.instance Puppet::Util::Instrumentation[res.name] = res nil # don't leak the listener end def destroy(request) listener = Puppet::Util::Instrumentation[request.key] raise "Listener #{request.key} hasn't been subscribed" unless listener Puppet::Util::Instrumentation.unsubscribe(listener) end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/instrumentation_listener/rest.rb���������������������������������0000664�0052762�0001160�00000000307�12650174557�025651� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/instrumentation_listener' require 'puppet/indirector/rest' class Puppet::Indirector::InstrumentationListener::Rest < Puppet::Indirector::REST desc "Undocumented." end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/instrumentation_probe.rb�����������������������������������������0000664�0052762�0001160�00000000132�12650174557�024152� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A stub class, so our constants work. class Puppet::Indirector::InstrumentationProbe end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/instrumentation_probe/�������������������������������������������0000775�0052762�0001160�00000000000�12650174565�023630� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/instrumentation_probe/local.rb�����������������������������������0000664�0052762�0001160�00000001274�12650174557�025254� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/instrumentation_probe' require 'puppet/indirector/code' require 'puppet/util/instrumentation/indirection_probe' class Puppet::Indirector::InstrumentationProbe::Local < Puppet::Indirector::Code desc "Undocumented." def find(request) end def search(request) probes = [] Puppet::Util::Instrumentation::Instrumentable.each_probe do |probe| probes << Puppet::Util::Instrumentation::IndirectionProbe.new("#{probe.klass}.#{probe.method}") end probes end def save(request) Puppet::Util::Instrumentation::Instrumentable.enable_probes end def destroy(request) Puppet::Util::Instrumentation::Instrumentable.disable_probes end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/instrumentation_probe/rest.rb������������������������������������0000664�0052762�0001160�00000000301�12650174557�025125� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/rest' require 'puppet/indirector/instrumentation_probe' class Puppet::Indirector::InstrumentationProbe::Rest < Puppet::Indirector::REST desc "Undocumented." end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/json.rb����������������������������������������������������������0000664�0052762�0001160�00000004253�12650174557�020501� 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 ~ three times faster despite being pure Ruby # rather than a C implementation. 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) } rescue TypeError => detail Puppet.log_exception "Could not save #{self.name} #{request.key}: #{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 #{self.name} #{request.key}: #{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 #{self.class}: #{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 = File.read(file) rescue Errno::ENOENT return nil rescue => detail raise Puppet::Error, "Could not read JSON data for #{indirection.name} #{key}: #{detail}", detail.backtrace end begin return from_json(json) rescue => detail raise Puppet::Error, "Could not parse JSON data for #{indirection.name} #{key}: #{detail}", detail.backtrace end end def from_json(text) model.convert_from('pson', text) end def to_json(object) object.render('pson') end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/key/�������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017766� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/key/ca.rb��������������������������������������������������������0000664�0052762�0001160�00000000567�12650174557�020707� 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 on disk. This terminus *only* works with the CA key, 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-3.8.5/lib/puppet/indirector/key/disabled_ca.rb�����������������������������������������������0000664�0052762�0001160�00000001206�12650174557�022525� 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-3.8.5/lib/puppet/indirector/key/file.rb������������������������������������������������������0000664�0052762�0001160�00000002356�12650174557�021241� 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.key} public key: #{detail}", detail.backtrace end end # Save the public key, in addition to the private key. def save(request) super begin Puppet.settings.setting(:publickeydir).open_file(public_key_path(request.key), 'w') do |f| f.print request.instance.content.public_key.to_pem end rescue => detail raise Puppet::Error, "Could not write #{request.key}: #{detail}", detail.backtrace end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/key/memory.rb����������������������������������������������������0000664�0052762�0001160�00000000275�12650174557�021630� 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-3.8.5/lib/puppet/indirector/ldap.rb����������������������������������������������������������0000664�0052762�0001160�00000004236�12650174557�020451� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/terminus' require 'puppet/util/ldap/connection' class Puppet::Indirector::Ldap < Puppet::Indirector::Terminus # 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 #{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 #{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 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}" Puppet.log_exception(detail, message) raise Puppet::Error, message, detail.backtrace end end @connection end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/memory.rb��������������������������������������������������������0000664�0052762�0001160�00000001342�12650174557�021034� 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.key} to destroy") 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-3.8.5/lib/puppet/indirector/msgpack.rb�������������������������������������������������������0000664�0052762�0001160�00000004511�12650174557�021152� 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 "Could not save #{self.name} #{request.key}: #{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 #{self.name} #{request.key}: #{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 #{self.class}: #{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 = File.read(file) rescue Errno::ENOENT return nil rescue => detail raise Puppet::Error, "Could not read MessagePack data for #{indirection.name} #{key}: #{detail}", detail.backtrace end begin return from_msgpack(msgpack) rescue => detail raise Puppet::Error, "Could not parse MessagePack data for #{indirection.name} #{key}: #{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-3.8.5/lib/puppet/indirector/node/������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020123� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/node/active_record.rb��������������������������������������������0000664�0052762�0001160�00000001245�12650174557�023264� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/rails/host' require 'puppet/indirector/active_record' require 'puppet/node' class Puppet::Node::ActiveRecord < Puppet::Indirector::ActiveRecord use_ar_model Puppet::Rails::Host desc "A component of ActiveRecord storeconfigs. ActiveRecord-based storeconfigs and inventory are deprecated. See http://links.puppetlabs.com/activerecord-deprecation" def initialize Puppet.deprecation_warning "ActiveRecord-based storeconfigs and inventory are deprecated. See http://links.puppetlabs.com/activerecord-deprecation" super end def find(request) node = super node.environment = request.environment node.fact_merge node end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/node/exec.rb�����������������������������������������������������0000664�0052762�0001160�00000003572�12650174557�021404� 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](http://docs.puppetlabs.com/guides/external_nodes.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) # 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) 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) 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 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 #{data[0].class}, not a string or symbol" end hash end rescue => detail raise Puppet::Error, "Could not load external node results for #{name}: #{detail}", detail.backtrace end end ��������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/node/ldap.rb�����������������������������������������������������0000664�0052762�0001160�00000016631�12650174557�021400� 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](http://docs.puppetlabs.com/guides/ldap_nodes.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`." # 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" 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) 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(dummy_argument=:work_arround_for_ruby_GC_bug) 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) result[:environment] = result[:parameters]["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, Fixnum, Bignum; 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}'")) 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) node = Puppet::Node.new(name) add_to_node(node, info) node.fact_merge 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" 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 def get_parameters_from_entry(entry) stacked_params = stacked_attributes entry.to_hash.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.dn} specifies more than one parent: #{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-3.8.5/lib/puppet/indirector/node/memory.rb���������������������������������������������������0000664�0052762�0001160�00000000663�12650174557�021766� 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-3.8.5/lib/puppet/indirector/node/msgpack.rb��������������������������������������������������0000664�0052762�0001160�00000000363�12650174557�022100� 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-3.8.5/lib/puppet/indirector/node/plain.rb����������������������������������������������������0000664�0052762�0001160�00000001175�12650174557�021560� 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 node.fact_merge node end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/node/rest.rb�����������������������������������������������������0000664�0052762�0001160�00000000341�12650174557�021424� 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-3.8.5/lib/puppet/indirector/node/store_configs.rb��������������������������������������������0000664�0052762�0001160�00000000343�12650174557�023315� 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-3.8.5/lib/puppet/indirector/node/write_only_yaml.rb������������������������������������������0000664�0052762�0001160�00000002075�12650174557�023672� 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 desc "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-3.8.5/lib/puppet/indirector/node/yaml.rb�����������������������������������������������������0000664�0052762�0001160�00000001421�12650174557�021411� 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) # This looks very strange because when the object is read from disk the # environment is a string and by assigning it back onto the object it gets # converted to a Puppet::Node::Environment. # # The Puppet::Node class can't handle this itself because we are loading # with just straight YAML, which doesn't give the object a chance to modify # things as it is loaded. Instead YAML simply sets the instance variable # and leaves it at that. object.environment = object.environment object end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/none.rb����������������������������������������������������������0000664�0052762�0001160�00000000305�12650174557�020461� 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-3.8.5/lib/puppet/indirector/plain.rb���������������������������������������������������������0000664�0052762�0001160�00000000401�12650174557�020622� 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-3.8.5/lib/puppet/indirector/queue.rb���������������������������������������������������������0000664�0052762�0001160�00000005433�12650174557�020655� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/terminus' require 'puppet/util/queue' require 'puppet/util' # Implements the <tt>:queue</tt> abstract indirector terminus type, for storing # model instances to a message queue, presumably for the purpose of out-of-process # handling of changes related to the model. # # Relies upon Puppet::Util::Queue for registry and client object management, # and specifies a default queue type of <tt>:stomp</tt>, appropriate for use with a variety of message brokers. # # It's up to the queue client type to instantiate itself correctly based on Puppet configuration information. # # A single queue client is maintained for the abstract terminus, meaning that you can only use one type # of queue client, one message broker solution, etc., with the indirection mechanism. # # Per-indirection queues are assumed, based on the indirection name. If the <tt>:catalog</tt> indirection makes # use of this <tt>:queue</tt> terminus, queue operations work against the "catalog" queue. It is up to the queue # client library to handle queue creation as necessary (for a number of popular queuing solutions, queue # creation is automatic and not a concern). class Puppet::Indirector::Queue < Puppet::Indirector::Terminus extend ::Puppet::Util::Queue include Puppet::Util def initialize(*args) super end # Queue has no idiomatic "find" def find(request) nil end # Place the request on the queue def save(request) result = nil benchmark :info, "Queued #{indirection.name} for #{request.key}" do result = client.publish_message(queue, request.instance.render(:pson)) end result rescue => detail msg = "Could not write #{request.key} to queue: #{detail}\nInstance::#{request.instance}\n client : #{client}" raise Puppet::Error, msg, detail.backtrace end def self.queue indirection_name end def queue self.class.queue end # Returns the singleton queue client object. def client self.class.client end # converts the _message_ from deserialized format to an actual model instance. def self.intern(message) result = nil benchmark :info, "Loaded queued #{indirection.name}" do result = model.convert_from(:pson, message) end result end # Provides queue subscription functionality; for a given indirection, use this method on the terminus # to subscribe to the indirection-specific queue. Your _block_ will be executed per new indirection # model received from the queue, with _obj_ being the model instance. def self.subscribe client.subscribe(queue) do |msg| begin yield(self.intern(msg)) rescue => detail Puppet.log_exception(detail, "Error occurred with subscription to queue #{queue} for indirection #{indirection_name}: #{detail}") end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/report/����������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020511� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/report/msgpack.rb������������������������������������������������0000664�0052762�0001160�00000000504�12650174557�022463� 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-3.8.5/lib/puppet/indirector/report/processor.rb����������������������������������������������0000664�0052762�0001160�00000003113�12650174557�023054� 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 #{name} failed: #{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}'" end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/report/rest.rb���������������������������������������������������0000664�0052762�0001160�00000000632�12650174557�022015� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/rest' 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) private def deserialize_save(content_type, body) format = Puppet::Network::FormatHandler.format_for(content_type) format.intern(Array, body) end end ������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/report/yaml.rb���������������������������������������������������0000664�0052762�0001160�00000000461�12650174557�022002� 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-3.8.5/lib/puppet/indirector/request.rb�������������������������������������������������������0000664�0052762�0001160�00000021154�12650174557�021217� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'cgi' require 'uri' require 'puppet/indirector' require 'puppet/util/pson' require 'puppet/network/resolver' # 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 attr_accessor :key, :method, :options, :instance, :node, :ip, :authenticated, :ignore_cache, :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, :instance, :environment] ::PSON.register_document_type('IndirectorRequest',self) def self.from_data_hash(data) raise ArgumentError, "No indirection name provided in data" unless indirection_name = data['type'] raise ArgumentError, "No method name provided in data" unless method = data['method'] raise ArgumentError, "No key provided in data" unless key = data['key'] request = new(indirection_name, method, key, nil, data['attributes']) if instance = data['instance'] klass = Puppet::Indirector::Indirection.instance(request.indirection_name).model if instance.is_a?(klass) request.instance = instance else request.instance = klass.from_data_hash(instance) end end request end def self.from_pson(json) Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.") self.from_data_hash(json) end def to_data_hash result = { 'type' => indirection_name, 'method' => method, 'key' => key } attributes = {} OPTION_ATTRIBUTES.each do |key| next unless value = send(key) attributes[key] = value end options.each do |opt, value| attributes[opt] = value end result['attributes'] = attributes unless attributes.empty? result['instance'] = instance if instance result end def to_pson_data_hash { 'document_type' => 'IndirectorRequest', 'data' => to_data_hash, } end def to_pson(*args) to_pson_data_hash.to_pson(*args) end # 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 elsif (current_environment = Puppet.lookup(:current_environment)).name == env current_environment else Puppet.lookup(:environments).get!(env) end end def escaped_key URI.escape(key) 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_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_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? # For backward compatibility with older (pre-3.3) masters, # this puppet option allows serialization of query parameter # arrays as yaml. This can be removed when we remove yaml # support entirely. if Puppet.settings[:legacy_query_parameter_serialization] replace_arrays_with_yaml end "?" + encode_params(expand_into_parameters(options.to_a)) end def replace_arrays_with_yaml options.each do |key, value| case value when Array options[key] = YAML.dump(value) end end 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, Fixnum, Bignum, Float params << [key, value] else raise ArgumentError, "HTTP REST queries cannot handle values of type '#{value.class}'" end end end def encode_params(params) params.collect do |key, value| "#{key}=#{CGI.escape(value.to_s)}" end.join("&") end def to_hash result = options.dup OPTION_ATTRIBUTES.each do |attribute| if value = send(attribute) result[attribute] = value end end result end def to_s return(uri ? uri : "/#{indirection_name}/#{key}") end def do_request(srv_service=:puppet, default_server=Puppet.settings[:server], default_port=Puppet.settings[:masterport], &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}: #{e.message}" end end end # ... Fall back onto the default server. Puppet.debug "No more servers left, falling back to #{default_server}:#{default_port}" if Puppet.settings[:use_srv_records] self.server = default_server self.port = default_port 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 uri = URI.parse(URI.escape(key)) rescue => detail raise ArgumentError, "Could not understand URL #{key}: #{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 @protocol = uri.scheme if uri.scheme == 'puppet' @key = URI.unescape(uri.path.sub(/^\//, '')) return end env, indirector, @key = URI.unescape(uri.path.sub(/^\//, '')).split('/',3) @key ||= '' self.environment = env unless env == '' end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/resource/��������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�021025� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/resource/active_record.rb����������������������������������������0000664�0052762�0001160�00000006171�12650174557�024171� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/active_record' require 'puppet/indirector/resource/validator' class Puppet::Resource::ActiveRecord < Puppet::Indirector::ActiveRecord include Puppet::Resource::Validator desc "A component of ActiveRecord storeconfigs. ActiveRecord-based storeconfigs and inventory are deprecated. See http://links.puppetlabs.com/activerecord-deprecation" def initialize Puppet.deprecation_warning "ActiveRecord-based storeconfigs and inventory are deprecated. See http://links.puppetlabs.com/activerecord-deprecation" super end def search(request) type = request_to_type_name(request) host = request.options[:host] filter = request.options[:filter] if filter and filter[1] =~ /^(and|or)$/i then raise Puppet::Error, "Complex search on StoreConfigs resources is not supported" end query = build_active_record_query(type, host, filter) Puppet::Rails::Resource.find(:all, query) end private def request_to_type_name(request) request.key.split('/', 2)[0] or raise "No key found in the request, failing: #{request.inspect}" end def filter_to_active_record(filter) # Don't call me if you don't have a filter, please. filter.is_a?(Array) or raise ArgumentError, "active record filters must be arrays" a, op, b = filter case op when /^(and|or)$/i then extra = [] first, args = filter_to_active_record a extra += args second, args = filter_to_active_record b extra += args return "(#{first}) #{op.upcase} (#{second})", extra when "==", "!=" then op = '=' if op == '==' # SQL, yayz! case a when "title" then return "title #{op} ?", [b] when "tag" then return "puppet_tags.name #{op} ?", [b] else return "param_names.name = ? AND param_values.value #{op} ?", [a, b] end else raise ArgumentError, "unknown operator #{op.inspect} in #{filter.inspect}" end end def build_active_record_query(type, host, filter) raise Puppet::DevError, "Cannot collect resources for a nil host" unless host search = "(exported=? AND restype=?)" arguments = [true, type] if filter then sql, values = filter_to_active_record(filter) search += " AND #{sql}" arguments += values end # note: we're not eagerly including any relations here because it can # create large numbers of objects that we will just throw out later. We # used to eagerly include param_names/values but the way the search filter # is built ruined those efforts and we were eagerly loading only the # searched parameter and not the other ones. query = {} case search when /puppet_tags/ query = { :joins => { :resource_tags => :puppet_tag } } when /param_name/ query = { :joins => { :param_values => :param_name } } end # We're going to collect objects from rails, but we don't want any # objects from this host. if host = Puppet::Rails::Host.find_by_name(host) search += " AND (host_id != ?)" arguments << host.id end query[:conditions] = [search, *arguments] query end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/resource/ral.rb��������������������������������������������������0000664�0052762�0001160�00000003604�12650174557�022134� 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? Puppet.deprecation_warning("Accessing resources on the network is deprecated. See http://links.puppetlabs.com/deprecate-networked-resource") super 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 #{type_name(request)}" end end ����������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/resource/rest.rb�������������������������������������������������0000664�0052762�0001160�00000001007�12650174557�022326� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/status' require 'puppet/indirector/rest' # @deprecated class Puppet::Resource::Rest < Puppet::Indirector::REST desc "Maniuplate resources remotely? Undocumented." private def deserialize_save(content_type, body) # Body is [ral_res.to_resource, transaction.report] format = Puppet::Network::FormatHandler.format_for(content_type) ary = format.intern(Array, body) [Puppet::Resource.from_data_hash(ary[0]), Puppet::Transaction::Report.from_data_hash(ary[1])] end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/resource/store_configs.rb����������������������������������������0000664�0052762�0001160�00000000744�12650174557�024224� 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? Puppet.deprecation_warning("Accessing resources on the network is deprecated. See http://links.puppetlabs.com/deprecate-networked-resource") super end end ����������������������������puppet-3.8.5/lib/puppet/indirector/resource/validator.rb��������������������������������������������0000664�0052762�0001160�00000000474�12650174557�023345� 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-3.8.5/lib/puppet/indirector/resource_type.rb�������������������������������������������������0000664�0052762�0001160�00000000174�12650174557�022416� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/resource/type' # A stub class, so our constants work. class Puppet::Indirector::ResourceType # :nodoc: end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/resource_type/���������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�022066� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/resource_type/parser.rb������������������������������������������0000664�0052762�0001160�00000007207�12650174557�023716� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/resource/type' require 'puppet/indirector/code' require 'puppet/indirector/resource_type' # The main terminus for Puppet::Resource::Type # # This exposes the known resource types from within Puppet. Only find # and search are supported. When a request is received, Puppet will # attempt to load all resource types (by parsing manifests and modules) and # returns a description of the resource types found. The format of these # objects is documented at {Puppet::Resource::Type}. # # @api public class Puppet::Indirector::ResourceType::Parser < Puppet::Indirector::Code desc "Return the data-form of a resource type." # Find will return the first resource_type with the given name. It is # not possible to specify the kind of the resource type. # # @param request [Puppet::Indirector::Request] The request object. # The only parameters used from the request are `environment` and # `key`, which corresponds to the resource type's `name` field. # @return [Puppet::Resource::Type, nil] # @api public def find(request) Puppet.override(:squelch_parse_errors => true) do krt = resource_types_in(request.environment) # This is a bit ugly. [:hostclass, :definition, :node].each do |type| # We have to us 'find_<type>' here because it will # load any missing types from disk, whereas the plain # '<type>' method only returns from memory. if r = krt.send("find_#{type}", [""], request.key) return r end end nil end end # Search for resource types using a regular expression. Unlike `find`, this # allows you to filter the results by the "kind" of the resource type # ("class", "defined_type", or "node"). All three are searched if no # `kind` filter is given. This also accepts the special string "`*`" # to return all resource type objects. # # @param request [Puppet::Indirector::Request] The request object. The # `key` field holds the regular expression used to search, and # `options[:kind]` holds the kind query parameter to filter the # result as described above. The `environment` field specifies the # environment used to load resources. # # @return [Array<Puppet::Resource::Type>, nil] # # @api public def search(request) Puppet.override(:squelch_parse_errors => true) do krt = resource_types_in(request.environment) # Make sure we've got all of the types loaded. krt.loader.import_all result_candidates = case request.options[:kind] when "class" krt.hostclasses.values when "defined_type" krt.definitions.values when "node" krt.nodes.values when nil result_candidates = [krt.hostclasses.values, krt.definitions.values, krt.nodes.values] else raise ArgumentError, "Unrecognized kind filter: " + "'#{request.options[:kind]}', expected one " + " of 'class', 'defined_type', or 'node'." end result = result_candidates.flatten.reject { |t| t.name == "" } return nil if result.empty? return result if request.key == "*" # Strip the regex of any wrapping slashes that might exist key = request.key.sub(/^\//, '').sub(/\/$/, '') begin regex = Regexp.new(key) rescue => detail raise ArgumentError, "Invalid regex '#{request.key}': #{detail}", detail.backtrace end result.reject! { |t| t.name.to_s !~ regex } return nil if result.empty? result end end def resource_types_in(environment) environment.check_for_reparse environment.known_resource_types end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/resource_type/rest.rb��������������������������������������������0000664�0052762�0001160�00000000363�12650174557�023373� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/resource/type' require 'puppet/indirector/rest' require 'puppet/indirector/resource_type' class Puppet::Indirector::ResourceType::Rest < Puppet::Indirector::REST desc "Retrieve resource types via a REST HTTP interface." end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/rest.rb����������������������������������������������������������0000664�0052762�0001160�00000020055�12650174557�020503� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'net/http' require 'uri' require 'puppet/network/http' require 'puppet/network/http_pool' require 'puppet/network/http/api/v1' require 'puppet/network/http/compression' # Access objects via REST class Puppet::Indirector::REST < Puppet::Indirector::Terminus include Puppet::Network::HTTP::Compression.module 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 def self.server Puppet.settings[server_setting || :server] end def self.port Puppet.settings[port_setting || :masterport].to_i end # Provide appropriate headers. def headers add_accept_encoding({"Accept" => model.supported_formats.join(", ")}) 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 = Puppet::Network::HTTP::API::V1.request_to_uri_and_body(request) uri_with_query_string = "#{uri}?#{body}" response = do_request(request) do |request| # 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 http_post(request, uri, body, headers) else http_get(request, uri_with_query_string, headers) end end if is_http_200?(response) check_master_version(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. # content_type, body = parse_response(response) msg = "Find #{elide(uri_with_query_string, 100)} resulted in 404 with the message: #{body}" raise Puppet::Error, msg else nil end end def head(request) response = do_request(request) do |request| http_head(request, Puppet::Network::HTTP::API::V1.indirection2uri(request), headers) end if is_http_200?(response) check_master_version(response) true else false end end def search(request) response = do_request(request) do |request| http_get(request, Puppet::Network::HTTP::API::V1.indirection2uri(request), headers) end if is_http_200?(response) check_master_version(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 |request| http_delete(request, Puppet::Network::HTTP::API::V1.indirection2uri(request), headers) end if is_http_200?(response) check_master_version(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 |request| http_put(request, Puppet::Network::HTTP::API::V1.indirection2uri(request), request.instance.render, headers.merge({ "Content-Type" => request.instance.mime })) end if is_http_200?(response) check_master_version(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) request.do_request(self.class.srv_service, self.class.server, self.class.port) { |request| yield(request) } 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) message = "Error #{response.code} on SERVER: #{(response.body||'').empty? ? response.message : uncompress_body(response)}" Net::HTTPError.new(message, response) end def check_master_version response if !response[Puppet::Network::HTTP::HEADER_PUPPET_VERSION] && (Puppet[:legacy_query_parameter_serialization] == false || Puppet[:report_serialization_format] != "yaml") Puppet.notice "Using less secure serialization of reports and query parameters for compatibility" Puppet.notice "with older puppet master. To remove this notice, please upgrade your master(s) " Puppet.notice "to Puppet 3.3 or newer." Puppet.notice "See http://links.puppetlabs.com/deprecate_yaml_on_network for more information." Puppet[:legacy_query_parameter_serialization] = true Puppet[:report_serialization_format] = "yaml" end 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*;.*$/,''), body = 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-3.8.5/lib/puppet/indirector/run/�������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020002� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/run/local.rb�����������������������������������������������������0000664�0052762�0001160�00000000451�12650174557�021422� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/run' require 'puppet/indirector/code' class Puppet::Run::Local < Puppet::Indirector::Code desc "Trigger a Puppet run locally. Only used internally." def save( request ) request.instance.run end def validate_key(request) # No key is necessary for kick end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/run/rest.rb������������������������������������������������������0000664�0052762�0001160�00000000374�12650174557�021311� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/run' require 'puppet/indirector/rest' class Puppet::Run::Rest < Puppet::Indirector::REST desc "Trigger Agent runs via REST." private def deserialize_save(content_type, body) model.convert_from(content_type, body) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/ssl_file.rb������������������������������������������������������0000664�0052762�0001160�00000012772�12650174557�021335� 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 #{self.class.name} cannot function" end def path(name) if name =~ Puppet::Indirector::BadNameRegexp then Puppet.crit("directory traversal detected in #{self.class}: #{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.key} at '#{path}'" begin Puppet::FileSystem.unlink(path) rescue => detail raise Puppet::Error, "Could not remove #{request.key}: #{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.key}; parent directory #{dir} does not exist") unless FileTest.directory?(dir) raise Puppet::Error.new("Cannot save #{request.key}; parent directory #{dir} is not writable") 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) 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}" 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) if ca?(name) and ca_location Puppet.settings.setting(self.class.ca_setting).open('w') { |f| yield f } elsif file_location Puppet.settings.setting(self.class.file_setting).open('w') { |f| yield f } elsif setting = self.class.directory_setting begin Puppet.settings.setting(setting).open_file(path, 'w') { |f| yield f } rescue => detail raise Puppet::Error, "Could not write #{path} to #{setting}: #{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-3.8.5/lib/puppet/indirector/status.rb��������������������������������������������������������0000664�0052762�0001160�00000000114�12650174557�021043� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A stub class, so our constants work. class Puppet::Indirector::Status end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/status/����������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020521� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/status/local.rb��������������������������������������������������0000664�0052762�0001160�00000000403�12650174557�022136� 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-3.8.5/lib/puppet/indirector/status/rest.rb���������������������������������������������������0000664�0052762�0001160�00000000415�12650174557�022024� 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-3.8.5/lib/puppet/indirector/store_configs.rb�������������������������������������������������0000664�0052762�0001160�00000001117�12650174557�022370� 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-3.8.5/lib/puppet/indirector/terminus.rb������������������������������������������������������0000664�0052762�0001160�00000012007�12650174557�021372� 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 #{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 # Our subclass is specifically associated with an indirection. raise("Invalid name #{longname}") unless names.length > 0 indirection_name = names.pop.sub(/^[A-Z]/) { |i| i.downcase }.gsub(/[A-Z]/) { |i| "_#{i.downcase}" }.intern if indirection_name == "" or indirection_name.nil? 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 = indirection_name # 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 #{request.instance.name.inspect} does not match requested key #{request.key.inspect}" end end def validate_model(request) unless model === request.instance raise Puppet::Indirector::ValidationError, "Invalid instance type #{request.instance.class.inspect}, expected #{model.inspect}" end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/indirector/yaml.rb����������������������������������������������������������0000664�0052762�0001160�00000003624�12650174557�020473� 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.name} #{request.key}: #{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 #{self.name} #{request.key}: #{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 #{self.class}: #{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-3.8.5/lib/puppet/interface.rb����������������������������������������������������������������0000664�0052762�0001160�00000016211�12650174557�017323� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/util/autoload' require 'prettyprint' require 'semver' # @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. # @api private # @deprecated def autoloader Puppet.deprecation_warning("Puppet::Interface.autoloader is deprecated; please use Puppet::Interface#loader instead") @autoloader ||= Puppet::Util::Autoload.new(:application, "puppet/face") end # Lists all loaded faces # @return [Array<Symbol>] 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, '<action>' end ######################################################################## # The name of the face # @return [Symbol] # @api private attr_reader :name # The version of the face # @return [SemVer] 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 SemVer.valid?(version) raise ArgumentError, "Cannot create face #{name.inspect} with invalid version number '#{version}'!" end @name = Puppet::Interface::FaceCollection.underscorize(name) @version = SemVer.new(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 ######################################################################## # 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-3.8.5/lib/puppet/interface/������������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�016774� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/interface/action.rb���������������������������������������������������������0000664�0052762�0001160�00000030365�12650174557�020606� 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 #{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 = "The second argument to set_rendering_method_for must be a Proc" msg += ", not #{proc.class.name}" unless proc.nil? raise ArgumentError, msg end if proc.arity != 1 and proc.arity != (@positional_arg_count + 1) msg = "the when_rendering method for the #{@face.name} face #{name} action " msg += "takes either just one argument, the result of when_invoked, " msg += "or the result plus the #{@positional_arg_count} arguments passed " msg += "to the when_invoked block, not " if proc.arity < 0 then msg += "a variable number" else msg += proc.arity.to_s end raise ArgumentError, msg end unless type.is_a? Symbol raise ArgumentError, "The rendering format must be a symbol, not #{type.class.name}" end if @when_rendering.has_key? type then raise ArgumentError, "You can't define a rendering method for #{type} twice" 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 ######################################################################## # 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 raise ArgumentError, "when_invoked requires at least one argument (options) for action #{@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}" elsif conflict = @face.get_option(name) then raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{@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| raise ArgumentError, "Global option #{refopt} does not exist in Puppet.settings" unless Puppet.settings.include? refopt @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? msg = overlap.map {|k, v| "(#{k}, #{v.sort.join(', ')})" }.join(", ") raise ArgumentError, "Multiple aliases for the same option passed: #{msg}" end unless unknown.empty? msg = unknown.sort.join(", ") raise ArgumentError, "Unknown options passed: #{msg}" 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? msg = missing.sort.join(', ') raise ArgumentError, "The following options are required: #{msg}" 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-3.8.5/lib/puppet/interface/action_builder.rb�������������������������������������������������0000664�0052762�0001160�00000012307�12650174557�022310� 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 # 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 raise ArgumentError, 'You must give a rendering format to when_rendering' end if block.nil? then 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 intial `-` 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) value.nil? and raise ArgumentError, "You must give a rendering format to render_as" formats = Puppet::Network::FormatHandler.formats unless formats.include? value raise ArgumentError, "#{value.inspect} is not a valid rendering format: #{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) @action.when_invoked or raise ArgumentError, "actions need to know what to do when_invoked; please add the block" end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/interface/action_manager.rb�������������������������������������������������0000664�0052762�0001160�00000006602�12650174557�022275� 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}" 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 # Defines an action without using ActionBuilder. The block given is # the code that will be executed when the action is invoked. # @api public # @deprecated def script(name, &block) @actions ||= {} Puppet.warning "Redefining action #{name} for #{self}" if action?(name) # REVISIT: (#18048) it's possible to create multiple default actions @actions[name] = Puppet::Interface::Action.new(self, name, :when_invoked => block) 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.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 # @api private def action?(name) actions.include?(name.to_sym) end end ������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/interface/documentation.rb��������������������������������������������������0000664�0052762�0001160�00000025336�12650174557�022204� 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 metheds. 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 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 raise ArgumentError, 'author must be a string; use multiple statements for multiple authors' end if value =~ /\n/ then 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 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 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 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 raise ArgumentError, "copyright with a year #{fault} is very strange; did you accidentally add or subtract two years?" 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+$/ } raise ArgumentError, "#{part.inspect} is not a good copyright year or range" end Range.new(found[0].to_i, found[1].to_i) else raise ArgumentError, "#{part.inspect} is not a good copyright year or range" 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 raise ArgumentError, "#{input.inspect} is not a good copyright year, set, or range" end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/interface/face_collection.rb������������������������������������������������0000664�0052762�0001160�00000011140�12650174557�022430� 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 |version| break if action = @faces[name][version].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 ] found = SemVer.find_matching(pattern, versions) return @faces[name][found] 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#{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 raise ArgumentError, "#{name.inspect} (#{name.class}) is not a valid face name" end name.to_s.downcase.split(/[-_]/).join('_').to_sym end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/interface/option.rb���������������������������������������������������������0000664�0052762�0001160�00000012507�12650174557�020637� 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, "#{item.inspect}: long options need two dashes (--)" 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, "#{item.inspect}: already defined in puppet" end if dup = dups[name] then raise ArgumentError, "#{item.inspect}: duplicates existing alias #{dup.inspect} in #{@parent}" else dups[name] = item end else raise ArgumentError, "#{item.inspect} is not valid for an option argument" 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" 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=~/[ =]\[/ } @optional_argument and raise ArgumentError, "Options with optional arguments are not supported" if @optional_argument and not @optparse.all? { |o| o=~/[ =]\[/ } then raise ArgumentError, "Option #{@name} is inconsistent about the argument being optional" 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.inspect}" end found.captures.first end # @api private def optparse_to_name(declaration) name = optparse_to_optionname(declaration).tr('-', '_') raise "#{name.inspect} is an invalid option name" unless name.to_s =~ /^[a-z]\w*$/ 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) required and raise ArgumentError, "#{self} can't be optional and have a default value" proc.is_a? Proc or raise ArgumentError, "default value for #{self} is a #{proc.class.name.inspect}, not a proc" @default = proc end def default @default and @default.call end attr_reader :parent, :name, :aliases, :optparse attr_accessor :required def required=(value) has_default? and raise ArgumentError, "#{self} can't be optional and have a default value" @required = value end attr_accessor :before_action def before_action=(proc) proc.is_a? Proc or raise ArgumentError, "before action hook for #{self} is a #{proc.class.name.inspect}, not a proc" @before_action = @parent.__send__(:__add_method, __decoration_name(:before), proc) end attr_accessor :after_action def after_action=(proc) proc.is_a? Proc or raise ArgumentError, "after action hook for #{self} is a #{proc.class.name.inspect}, not a proc" @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-3.8.5/lib/puppet/interface/option_builder.rb�������������������������������������������������0000664�0052762�0001160�00000006122�12650174557�022341� 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) block or raise ArgumentError, "#{@option} before_action requires a block" if @option.before_action raise ArgumentError, "#{@option} already has a before_action set" end unless block.arity == 3 then 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) block or raise ArgumentError, "#{@option} after_action requires a block" if @option.after_action raise ArgumentError, "#{@option} already has an after_action set" end unless block.arity == 3 then 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) block or raise ArgumentError, "#{@option} default_to requires a block" if @option.has_default? raise ArgumentError, "#{@option} already has a default value" end # Ruby 1.8 treats a block without arguments as accepting any number; 1.9 # gets this right, so we work around it for now... --daniel 2011-07-20 unless block.arity == 0 or (RUBY_VERSION =~ /^1\.8/ and block.arity == -1) raise ArgumentError, "#{@option} default_to block should not take any arguments" end @option.default = block end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/interface/option_manager.rb�������������������������������������������������0000664�0052762�0001160�00000005436�12650174557�022334� 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| raise ArgumentError, "Global option #{refopt} does not exist in Puppet.settings" unless Puppet.settings.include? refopt @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}" 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}" 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-3.8.5/lib/puppet/loaders.rb������������������������������������������������������������������0000664�0052762�0001160�00000001136�12650174557�017014� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet module Pops require 'puppet/pops/loaders' module Loader 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/ruby_function_instantiator' require 'puppet/pops/loader/loader_paths' require 'puppet/pops/loader/simple_environment_loader' end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/metatype/�������������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�016664� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/metatype/manager.rb���������������������������������������������������������0000664�0052762�0001160�00000013236�12650174557�020631� 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 # 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) Puppet.warning "Puppet::Type.newtype(#{name}) now expects a hash as the second argument, not #{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) if options.include?(:parent) Puppet.deprecation_warning "option :parent is deprecated. It has no effect" options.delete(:parent) end # 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'. Puppet.warning "'new#{name.to_s}' method already exists; skipping" 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? 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[name] # Try mangling the name, if it is a string. if name.is_a? String name = name.downcase.intern return @types[name] if @types[name] end # Try loading the type. if typeloader.load(name, Puppet.lookup(:current_environment)) Puppet.warning "Loaded puppet/type/#{name} but no class was created" unless @types.include? name 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", :wrap => false) end @typeloader end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module.rb�������������������������������������������������������������������0000664�0052762�0001160�00000023133�12650174557�016651� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/logging' require 'semver' require 'json' # 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 InvalidName < Error; end class InvalidFilePattern < Error; end include Puppet::Util::Logging FILETYPES = { "manifests" => "manifests", "files" => "files", "templates" => "templates", "plugins" => "lib", "pluginfacts" => "facts.d", } # 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 attr_reader :name, :environment, :path, :metadata attr_writer :environment attr_accessor :dependencies, :forge_name attr_accessor :source, :author, :version, :license, :puppetversion, :summary, :description, :project_page def initialize(name, path, environment) @name = name @path = path @environment = environment assert_validity load_metadata if has_metadata? validate_puppet_version @absolute_path_to_manifests = Puppet::FileSystem::PathPattern.absolute(manifests) end def has_metadata? return false unless metadata_file return false unless Puppet::FileSystem.exist?(metadata_file) begin metadata = JSON.parse(File.read(metadata_file)) rescue JSON::JSONError => e Puppet.debug("#{name} has an invalid and unparsable metadata.json file. The parse error: #{e.message}") return false end return metadata.is_a?(Hash) && !metadata.keys.empty? 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 license_file return @license_file if defined?(@license_file) return @license_file = nil unless path @license_file = File.join(path, "License") end def load_metadata @metadata = data = JSON.parse(File.read(metadata_file)) @forge_name = data['name'].gsub('-', '/') if data['name'] [:source, :author, :version, :license, :puppetversion, :dependencies].each do |attr| unless value = data[attr.to_s] unless attr == :puppetversion raise MissingMetadata, "No #{attr} module metadata provided for #{self.name}" end end # NOTICE: The fallback to `versionRequirement` is something we'd like to # not have to support, but we have a reasonable number of releases that # don't use `version_requirement`. When we can deprecate this, we should. if attr == :dependencies value.tap do |dependencies| dependencies.each do |dep| dep['version_requirement'] ||= dep['versionRequirement'] || '>= 0.0.0' end end end send(attr.to_s + "=", value) end end # Return the list of manifests matching the given glob pattern, # defaulting to 'init.{pp,rb}' 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_manifests = [manifest("init.pp"), manifest("init.rb")].compact init_manifests + searched_manifests end def all_manifests return [] unless Puppet::FileSystem.exist?(manifests) Dir.glob(File.join(manifests, '**', '*.{rb,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 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 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| author, 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 def has_local_changes? Puppet.deprecation_warning("This method is being removed.") require 'puppet/module_tool/applications' changes = Puppet::ModuleTool::Applications::Checksummer.run(path) !changes.empty? end def local_changes Puppet.deprecation_warning("This method is being removed.") require 'puppet/module_tool/applications' Puppet::ModuleTool::Applications::Checksummer.run(path) 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| forge_name = dependency['name'] version_string = dependency['version_requirement'] || '>= 0.0.0' dep_mod = begin environment.module_by_forge_name(forge_name) rescue nil end error_details = { :name => forge_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 = SemVer[version_string] actual_version_semver = SemVer.new(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 validate_puppet_version return unless puppetversion and puppetversion != Puppet.version raise IncompatibleModule, "Module #{self.name} is only compatible with Puppet version #{puppetversion}, not #{Puppet.version}" end private def wanted_manifests_from(pattern) begin extended = File.extname(pattern).empty? ? "#{pattern}.{pp,rb}" : 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 raise InvalidName, "Invalid module name #{name}; module names must be alphanumeric (plus '-'), not '#{name}'" unless name =~ /^[-\w]+$/ end def ==(other) self.name == other.name && self.version == other.version && self.path == other.path && self.environment == other.environment end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool.rb��������������������������������������������������������������0000664�0052762�0001160�00000015751�12650174557�017715� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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}" end end # Find the module root when given a path by checking each directory up from # its current location until it finds one that contains a file called # 'Modulefile'. # # @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' or 'Modulefile' in the directory. # # @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') || FileTest.file?(path + 'Modulefile') 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<Symbol,String>] 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 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 supercede 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 reguest 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 # {Semantic::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 # @return [Array(String, Semantic::VersionRange, String)] an tuple of the # dependent module's name, the version range dependency, and the # unparsed range expression. def self.parse_module_dependency(where, dep) dep_name = dep['name'].tr('/', '-') range = dep['version_requirement'] || dep['versionRequirement'] || '>= 0.0.0' begin parsed_range = Semantic::VersionRange.parse(range) rescue ArgumentError => e Puppet.debug "Error in #{where} parsing dependency #{dep_name} (#{e.message}); using empty range." parsed_range = Semantic::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/module_tool/modulefile' require 'puppet/forge/cache' require 'puppet/forge' �����������������������puppet-3.8.5/lib/puppet/module_tool/����������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017356� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/applications.rb�������������������������������������������������0000664�0052762�0001160�00000001031�12650174557�022365� 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-3.8.5/lib/puppet/module_tool/applications/���������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�022044� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/applications/application.rb�������������������������������������0000664�0052762�0001160�00000005613�12650174557�024702� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'net/http' require 'semver' require '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 = JSON.parse(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 or Modulefile in module root at #{@path} See http://links.puppetlabs.com/modulefile for required file format." end modulefile_path = File.join(@path, 'Modulefile') metadata_path = File.join(@path, 'metadata.json') if File.file?(metadata_path) File.open(metadata_path) do |f| begin @metadata.update(JSON.load(f)) rescue JSON::ParserError => ex raise ArgumentError, "Could not parse JSON #{metadata_path}", ex.backtrace end end end if File.file?(modulefile_path) if File.file?(metadata_path) Puppet.warning "Modulefile is deprecated. Merging your Modulefile and metadata.json." else Puppet.warning "Modulefile is deprecated. Building metadata.json from Modulefile." end Puppet::ModuleTool::ModulefileReader.evaluate(@metadata, modulefile_path) 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})" end unless SemVer.valid?(version) raise ArgumentError, "Invalid version format: #{version} (Semantic Versions are acceptable: http://semver.org)" end return { :module_name => module_name, :author => author, :dir_name => shortname, :version => version } end end end end ���������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/applications/builder.rb�����������������������������������������0000664�0052762�0001160�00000011017�12650174557�024020� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'fileutils' require 'json' require 'puppet/file_system' require 'pathspec' 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 load_metadata! create_directory copy_contents write_json Puppet.notice "Building #{@path} for release" 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 File.read(pmtignore) elsif File.file? gitignore @ignored_files = PathSpec.new File.read(gitignore) 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 #{s.relative_path_from mpath} -> #{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. File.open(metadata_path, 'w') do |f| f.write(metadata.to_json) end File.open(File.join(build_path, 'checksums.json'), 'w') do |f| f.write(PSON.pretty_generate(Checksums.new(build_path))) end end def build_path @build_path ||= File.join(@pkg_path, metadata.release_name) end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/applications/checksummer.rb�������������������������������������0000664�0052762�0001160�00000003325�12650174557�024703� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require '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? JSON.parse(checksums_file.read) elsif metadata_file.exist? # Check metadata.json too; legacy modules store their checksums there. JSON.parse(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-3.8.5/lib/puppet/module_tool/applications/installer.rb���������������������������������������0000664�0052762�0001160�00000031251�12650174557�024371� 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 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 Semantic::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 Semantic::Dependency.add_source(installed_modules_source) Semantic::Dependency.add_source(module_repository) end else Semantic::Dependency.add_source(installed_modules_source) unless forced? Semantic::Dependency.add_source(module_repository) end end def run name = @name.tr('/', '-') version = options[:version] || '>= 0.0.0' results = { :action => :install, :module_name => name, :module_version => version } begin if mod = installed_modules[name] unless forced? if Semantic::VersionRange.parse(version).include? mod.version results[:result] = :noop results[:version] = mod.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 #{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. ">=#{version} #{version.major}.x".tap do |range| graph.add_constraint('installed', mod, range) do |node| Semantic::VersionRange.parse(range).include? node.version end end release.mod.dependencies.each do |dep| dep_name = dep['name'].tr('/', '-') dep['version_requirement'].tap do |range| graph.add_constraint("#{mod} constraint", dep_name, range) do |node| Semantic::VersionRange.parse(range).include? node.version end 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 = Semantic::Dependency.resolve(graph) rescue Semantic::Dependency::UnsatisfiableGraph raise NoVersionsSatisfyError, results.merge(:requested_name => name) end unless forced? # Check for module name conflicts. 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 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 end def local_tarball_source @tarball_source ||= begin Puppet::ModuleTool::LocalTarball.new(@name) 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 = Semantic::VersionRange.parse(version) graph = Semantic::Dependency::Graph.new(name => range) releases = Semantic::Dependency.fetch_releases(name) releases.each { |release| release.dependencies.clear } graph << releases end def build_dependency_graph(name, version) Semantic::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 => SemVer.new(@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-3.8.5/lib/puppet/module_tool/applications/searcher.rb����������������������������������������0000664�0052762�0001160�00000001250�12650174557�024164� 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 #{@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-3.8.5/lib/puppet/module_tool/applications/uninstaller.rb�������������������������������������0000664�0052762�0001160�00000007162�12650174557�024740� 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] end def run 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 SemVer[@options[:version]].include?(SemVer.new(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-3.8.5/lib/puppet/module_tool/applications/unpacker.rb����������������������������������������0000664�0052762�0001160�00000005674�12650174557�024216� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'pathname' require 'tmpdir' require '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 #{s.relative_path_from tmpdirpath}->#{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: #{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 = JSON.parse((root_dir + 'metadata.json').read) name = 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-3.8.5/lib/puppet/module_tool/applications/upgrader.rb����������������������������������������0000664�0052762�0001160�00000024206�12650174557�024207� 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] Semantic::Dependency.add_source(installed_modules_source) Semantic::Dependency.add_source(module_repository) end def run 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 `Semantic::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] = Semantic::Version.parse(mod.version) dir = Pathname.new(mod.modulepath) vstring = mod.version ? "v#{mod.version}" : '???' Puppet.notice "Found '#{name}' (#{colorize(:cyan, vstring)}) in #{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 = Semantic::VersionRange.parse(results[:requested_version]) 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 #{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 |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. ">=#{version} #{version.major}.x".tap do |range| graph.add_constraint('installed', mod, range) do |node| Semantic::VersionRange.parse(range).include? node.version end end release.mod.dependencies.each do |dep| dep_name = dep['name'].tr('/', '-') dep['version_requirement'].tap do |range| graph.add_constraint("#{mod} constraint", dep_name, range) do |node| Semantic::VersionRange.parse(range).include? node.version end end end end end begin Puppet.info "Resolving dependencies ..." releases = Semantic::Dependency.resolve(graph) rescue Semantic::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 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 = Semantic::VersionRange.parse(version) graph = Semantic::Dependency::Graph.new(name => range) releases = Semantic::Dependency.fetch_releases(name) releases.each { |release| release.dependencies.clear } graph << releases end def build_dependency_graph(name, version) Semantic::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-3.8.5/lib/puppet/module_tool/checksums.rb����������������������������������������������������0000664�0052762�0001160�00000002303�12650174557�021667� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'digest/md5' require 'puppet/network/format_support' module Puppet::ModuleTool # = Checksums # # This class proides 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-3.8.5/lib/puppet/module_tool/contents_description.rb�����������������������������������������0000664�0052762�0001160�00000005431�12650174557�024147� 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 |type_name| if type = Puppet::Type.type(type_name.to_sym) type_hash = {:name => type_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: #{type_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) [].tap do |attrs| type.allattrs.each do |name| if type.attrtype(name) == kind && name != :provider attrs.push(:name => name, :doc => type.attrclass(name).doc) end end end end # Return an array of hashes representing this +type+'s providers, each # containing :name and :doc. def provider_doc(type) [].tap do |providers| type.providers.sort.each do |prov| providers.push(:name => prov, :doc => type.provider(prov).doc) end end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/dependency.rb���������������������������������������������������0000664�0052762�0001160�00000002712�12650174557�022024� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/module_tool' require 'puppet/network/format_support' module Puppet::ModuleTool class Dependency include Puppet::Network::FormatSupport alias :to_json :to_pson 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 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-3.8.5/lib/puppet/module_tool/errors.rb�������������������������������������������������������0000664�0052762�0001160�00000000505�12650174557�021220� 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-3.8.5/lib/puppet/module_tool/errors/���������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020672� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/errors/base.rb��������������������������������������������������0000664�0052762�0001160�00000000544�12650174557�022135� 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-3.8.5/lib/puppet/module_tool/errors/installer.rb���������������������������������������������0000664�0052762�0001160�00000006467�12650174557�023232� 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}' (#{@requested_version}) requested; '#{@module_name}' (#{@installed_version}) already installed" end def multiline message = [] message << "Could not install module '#{@module_name}' (#{@requested_version})" message << " Module '#{@module_name}' (#{@installed_version}) is already installed" message << " Installed module has had changes made locally" unless @local_changes.empty? message << " Use `puppet module upgrade` to install a different version" 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}" end def multiline message = [] message << "Could not install '#{@requested_package}'" message << " No releases are available from #{@source}" message << " Does '#{@requested_package}' have at least one published release?" 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("'#{@requested_module}' (#{@requested_version}) requested; Path #{@directory} is not a directory.", original) end def multiline <<-MSG.strip Could not install module '#{@requested_module}' (#{@requested_version}) Path '#{@directory}' exists but is not a directory. A potential solution is to rename the path and then mkdir -p '#{@directory}' MSG end end class PermissionDeniedCreateInstallDirectoryError < InstallError def initialize(original, options) @requested_module = options[:requested_module] @requested_version = options[:requested_version] @directory = options[:directory] super("'#{@requested_module}' (#{@requested_version}) requested; Permission is denied to create #{@directory}.", original) end def multiline <<-MSG.strip Could not install module '#{@requested_module}' (#{@requested_version}) Permission is denied when trying to create directory '#{@directory}'. A potential solution is to check the ownership and permissions of parent directories. MSG end end class InvalidPathInPackageError < InstallError def initialize(options) @entry_path = options[:entry_path] @directory = options[:directory] super "Attempt to install file into #{@entry_path.inspect} under #{@directory.inspect}" end def multiline <<-MSG.strip Could not install package Package attempted to install file into #{@entry_path.inspect} under #{@directory.inspect}. MSG end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/errors/shared.rb������������������������������������������������0000664�0052762�0001160�00000015335�12650174557�022475� 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} '#{@requested_name}' (#{vstring}); no version satisfies all dependencies" end def multiline message = [] message << "Could not #{@action} module '#{@requested_name}' (#{vstring})" message << " No version of '#{@requested_name}' can satisfy all dependencies" message << " Use `puppet module #{@action} --ignore-dependencies` to #{@action} only this module" 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}" else super "Could not #{@action} '#{@module_name}'; no releases matching '#{@requested_version}' are available from #{@source}" end end def multiline message = [] message << "Could not #{@action} '#{@module_name}' (#{vstring})" if @requested_version == :latest message << " No releases are available from #{@source}" message << " Does '#{@module_name}' have at least one published release?" else message << " No releases matching '#{@requested_version}' are available from #{@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 "'#{@requested_module}' (#{@requested_version}) requested; installation conflict" end def multiline message = [] message << "Could not install module '#{@requested_module}' (#{@requested_version})" if @dependency message << " Dependency '#{@dependency[:name]}' (#{v(@dependency[:version])}) would overwrite #{@directory}" else message << " Installation would overwrite #{@directory}" end if @metadata message << " Currently, '#{@metadata["name"]}' (#{v(@metadata["version"])}) is installed to that directory" end if @dependency message << " Use `puppet module install --ignore-dependencies` to install only this module" else 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 "'#{@requested_module}' (#{v(@requested_version)}) requested; Invalid dependency cycle" end def multiline trace = [] trace << "You specified '#{@source.first[:name]}' (#{v(@requested_version)})" trace += @source[1..-1].map { |m| "which depends on '#{m[:name]}' (#{v(m[:version])})" } message = [] message << "Could not install module '#{@requested_module}' (#{v(@requested_version)})" message << " No version of '#{@module_name}' will satisfy dependencies" message << trace.map { |s| " #{s}" }.join(",\n") 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" end def multiline message = [] message << "Could not #{@action} module '#{@module_name}'" message << " Module '#{@module_name}' is not installed" message += @suggestions.map do |suggestion| " You may have meant `puppet module #{@action} #{suggestion}`" end 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] super "Could not #{@action} '#{@module_name}'; module appears in multiple places in the module path" end def multiline message = [] message << "Could not #{@action} module '#{@module_name}'" message << " Module '#{@module_name}' appears multiple places in the module path" message += @modules.map do |mod| " '#{@module_name}' (#{v(mod.version)}) was found in #{mod.modulepath}" end 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" end def multiline message = [] message << "Could not #{@action} module '#{@module_name}' (#{vstring})" message << " Installed module has had changes made locally" message << " Use `puppet module #{@action} --ignore-changes` to #{@action} this module anyway" message.join("\n") end end class InvalidModuleError < ModuleToolError def initialize(name, options) @name = name @action = options[:action] @error = options[:error] super "Could not #{@action} '#{@name}'; #{@error.message}" end def multiline message = [] message << "Could not #{@action} module '#{@name}'" message << " Failure trying to parse metadata" message << " Original message was: #{@error.message}" message.join("\n") end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/errors/uninstaller.rb�������������������������������������������0000664�0052762�0001160�00000003277�12650174557�023571� 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" end def multiline message = [] message << "Could not uninstall module '#{@module_name}' (#{v(@version)})" message << " No installed version of '#{@module_name}' matches (#{v(@version)})" message += @modules.map do |mod| " '#{mod[:name]}' (#{v(mod[:version])}) is installed in #{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" end def multiline message = [] message << ("Could not uninstall module '#{@module_name}'" << (@requested_version ? " (#{v(@requested_version)})" : '')) message << " Other installed modules have dependencies on '#{@module_name}' (#{v(@installed_version)})" message += @required_by.map do |mod| " '#{mod['name']}' (#{v(mod['version'])}) requires '#{@module_name}' (#{v(mod['version_requirement'])})" end message << " Use `puppet module uninstall --force` to uninstall this module anyway" message.join("\n") end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/errors/upgrader.rb����������������������������������������������0000664�0052762�0001160�00000004247�12650174557�023040� 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" end def multiline message = [] message << "Could not upgrade module '#{@module_name}' (#{vstring})" if @newer_versions.empty? message << " The installed version is already the latest version matching #{vstring}" else message << " There are #{@newer_versions.length} newer versions" 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 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}' (#{vstring}); downgrades are not allowed" end def multiline message = [] message << "Could not #{@action} module '#{@module_name}' (#{vstring})" message << " Downgrading is not allowed." message.join("\n") end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/install_directory.rb��������������������������������������������0000664�0052762�0001160�00000002366�12650174557�023445� 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 #{@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-3.8.5/lib/puppet/module_tool/installed_modules.rb��������������������������������������������0000664�0052762�0001160�00000005121�12650174557�023412� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'pathname' require 'puppet/forge' require 'puppet/module_tool' module Puppet::ModuleTool class InstalledModules < Semantic::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<Semantic::Dependency::ModuleRelease>] a list of releases for # the given name # @see Semantic::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 < Semantic::Dependency::ModuleRelease attr_reader :mod, :metadata def initialize(source, mod) @mod = mod @metadata = mod.metadata name = mod.forge_name.tr('/', '-') begin version = Semantic::Version.parse(mod.version) rescue Semantic::Version::ValidationFailure => e Puppet.warning "#{mod.name} (#{mod.path}) has an invalid version number (#{mod.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)." version = Semantic::Version.parse("0.0.0") end release = "#{name}@#{version}" super(source, name, version, {}) if mod.dependencies mod.dependencies.each do |dep| results = Puppet::ModuleTool.parse_module_dependency(release, dep) dep_name, parsed_range, range = results dep.tap do |dep| add_constraint('initialize', dep_name, range.to_s) do |node| parsed_range === node.version end 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-3.8.5/lib/puppet/module_tool/local_tarball.rb������������������������������������������������0000664�0052762�0001160�00000004411�12650174557�022477� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'pathname' require 'tmpdir' require 'puppet/forge' require 'puppet/module_tool' module Puppet::ModuleTool class LocalTarball < Semantic::Dependency::Source attr_accessor :release def initialize(filename) unpack(filename, tmpdir) Puppet.debug "Unpacked local tarball to #{tmpdir}" mod = Puppet::Module.new('tarball', tmpdir, nil) @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 < Semantic::Dependency::ModuleRelease attr_reader :mod, :install_dir, :metadata def initialize(source, mod) @mod = mod @metadata = mod.metadata name = mod.forge_name.tr('/', '-') version = Semantic::Version.parse(mod.version) release = "#{name}@#{version}" if mod.dependencies dependencies = mod.dependencies.map do |dep| Puppet::ModuleTool.parse_module_dependency(release, dep)[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: #{e.message}" end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/metadata.rb�����������������������������������������������������0000664�0052762�0001160�00000015330�12650174557�021466� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/methodhelper' require 'puppet/module_tool' require 'puppet/network/format_support' require 'uri' require '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, } 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'] 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 #{full_module_name}: Dependency #{name} was given conflicting version requirements #{version_requirement} and #{dup.version_requirement}. Verify that there are no duplicates in the metadata.json or the Modulefile." 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 # refering 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) # This is used to simulate an ordered hash. In particular, some keys # are promoted to the top of the serialized hash (while others are # demoted) for human-friendliness. # # This particularly works around the lack of ordered hashes in 1.8.7. promoted_keys = %w[ name version author summary license source ] demoted_keys = %w[ dependencies ] keys = data.keys keys -= promoted_keys keys -= demoted_keys contents = (promoted_keys + keys + demoted_keys).map do |k| value = (JSON.pretty_generate(data[k]) rescue data[k].to_json) "#{k.to_json}: #{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 # 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}" end # Validates that the version string can be parsed as per SemVer. def validate_version(version) return if SemVer.valid?(version) err = "version string cannot be parsed as a valid Semantic Version" raise ArgumentError, "Invalid 'version' field in metadata.json: #{err}" end # Validates that the version range can be parsed by Semantic. def validate_version_range(version_range) Semantic::VersionRange.parse(version_range) rescue ArgumentError => e raise ArgumentError, "Invalid 'version_range' field in metadata.json: #{e}" end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/modulefile.rb���������������������������������������������������0000664�0052762�0001160�00000004333�12650174557�022034� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/module_tool' require 'puppet/module_tool/dependency' module Puppet::ModuleTool # = Modulefile # # This class provides the DSL used for evaluating the module's 'Modulefile'. # These methods are used to concisely define this module's attributes, which # are later rendered as PSON into a 'metadata.json' file. class ModulefileReader # Read the +filename+ and eval its Ruby code to set values in the Metadata # +metadata+ instance. def self.evaluate(metadata, filename) builder = new(metadata) if File.file?(filename) builder.instance_eval(File.read(filename.to_s), filename.to_s, 1) else Puppet.warning "No Modulefile: #{filename}" end return builder end # Instantiate with the Metadata +metadata+ instance. def initialize(metadata) @metadata = metadata end # Set the +full_module_name+ (e.g. "myuser-mymodule"), which will also set the # +username+ and module +name+. Required. def name(name) @metadata.update('name' => name) end # Set the module +version+ (e.g., "0.1.0"). Required. def version(version) @metadata.update('version' => version) end # Add a dependency with the full_module_name +name+ (e.g. "myuser-mymodule"), an # optional +version_requirement+ (e.g. "0.1.0") and +repository+ (a URL # string). Optional. Can be called multiple times to add many dependencies. def dependency(name, version_requirement = nil, repository = nil) @metadata.add_dependency(name, version_requirement, repository) end # Set the source def source(source) @metadata.update('source' => source) end # Set the author or default to +username+ def author(author) @metadata.update('author' => author) end # Set the license def license(license) @metadata.update('license' => license) end # Set the summary def summary(summary) @metadata.update('summary' => summary) end # Set the description def description(description) @metadata.update('description' => description) end # Set the project page def project_page(project_page) @metadata.update('project_page' => project_page) end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/shared_behaviors.rb���������������������������������������������0000664�0052762�0001160�00000014530�12650174557�023217� 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 #{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 = SemVer.new(rel['version'] || '0.0.0') rescue SemVer::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 = SemVer[@version] rescue SemVer['>= 0.0.0'] else range = (@conditions[mod]).map do |r| SemVer[r[:dependency]] rescue SemVer['>= 0.0.0'] 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 === SemVer.new(@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: #{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-3.8.5/lib/puppet/module_tool/skeleton/�������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�021202� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/skeleton/templates/���������������������������������������������0000775�0052762�0001160�00000000000�12650174565�023200� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/skeleton/templates/generator/�����������������������������������0000775�0052762�0001160�00000000000�12650174565�025166� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/skeleton/templates/generator/Gemfile����������������������������0000664�0052762�0001160�00000000362�12650174557�026463� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������source 'https://rubygems.org' puppetversion = ENV.key?('PUPPET_VERSION') ? "= #{ENV['PUPPET_VERSION']}" : ['>= 3.3'] gem 'puppet', puppetversion gem 'puppetlabs_spec_helper', '>= 0.1.0' gem 'puppet-lint', '>= 0.3.2' gem 'facter', '>= 1.7.0' ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/skeleton/templates/generator/README.md.erb����������������������0000664�0052762�0001160�00000005713�12650174557�027223� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# <%= metadata.name %> #### Table of Contents 1. [Overview](#overview) 2. [Module Description - What the module does and why it is useful](#module-description) 3. [Setup - The basics of getting started with <%= metadata.name %>](#setup) * [What <%= metadata.name %> affects](#what-<%= metadata.name %>-affects) * [Setup requirements](#setup-requirements) * [Beginning with <%= metadata.name %>](#beginning-with-<%= metadata.name %>) 4. [Usage - Configuration options and additional functionality](#usage) 5. [Reference - An under-the-hood peek at what the module is doing and how](#reference) 5. [Limitations - OS compatibility, etc.](#limitations) 6. [Development - Guide for contributing to the module](#development) ## Overview A one-maybe-two sentence summary of what the module does/what problem it solves. This is your 30 second elevator pitch for your module. Consider including OS/Puppet version it works with. ## Module Description If applicable, this section should have a brief description of the technology the module integrates with and what that integration enables. This section 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 * A list of files, packages, services, or operations that the module will alter, impact, or execute on the system it's installed on. * This is a great place to stick any warnings. * Can be in list or paragraph form. ### Setup Requirements **OPTIONAL** If your module requires anything extra before setting up (pluginsync enabled, etc.), mention it here. ### Beginning with <%= metadata.name %> The very basic steps needed for a user to get the module up and running. If your most recent release breaks compatibility or requires particular steps for upgrading, you may wish to include an additional section here: Upgrading (For an example, see http://forge.puppetlabs.com/puppetlabs/firewall). ## Usage Put the classes, types, and resources for customizing, configuring, and doing the fancy stuff with your module here. ## Reference Here, list the classes, types, providers, facts, etc contained in your module. This section should include all of the under-the-hood workings of your module so people know what the module is touching on their system but don't need to mess with things. (We are working on automating this section!) ## Limitations This is where you list OS compatibility, version compatibility, etc. ## 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 may also add any additional sections you feel are necessary or important to include here. Please use the `## ` header. �����������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/skeleton/templates/generator/Rakefile���������������������������0000664�0052762�0001160�00000001171�12650174557�026634� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'rubygems' require 'puppetlabs_spec_helper/rake_tasks' require 'puppet-lint/tasks/puppet-lint' PuppetLint.configuration.send('disable_80chars') PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp"] desc "Validate manifests, templates, and ruby files" task :validate do Dir['manifests/**/*.pp'].each do |manifest| sh "puppet parser validate --noop #{manifest}" end Dir['spec/**/*.rb','lib/**/*.rb'].each do |ruby_file| sh "ruby -c #{ruby_file}" unless ruby_file =~ /spec\/fixtures/ end Dir['templates/**/*.erb'].each do |template| sh "erb -P -x -T '-' #{template} | ruby -c" end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/skeleton/templates/generator/manifests/�������������������������0000775�0052762�0001160�00000000000�12650174565�027157� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/skeleton/templates/generator/manifests/init.pp.erb��������������0000664�0052762�0001160�00000002025�12650174557�031232� 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 funtion 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 # # 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-3.8.5/lib/puppet/module_tool/skeleton/templates/generator/metadata.json.erb������������������0000664�0052762�0001160�00000000030�12650174557�030402� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<%= metadata.to_json %> ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/skeleton/templates/generator/spec/������������������������������0000775�0052762�0001160�00000000000�12650174565�026120� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/skeleton/templates/generator/spec/classes/����������������������0000775�0052762�0001160�00000000000�12650174565�027555� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/skeleton/templates/generator/spec/classes/init_spec.rb.erb������0000664�0052762�0001160�00000000254�12650174557�032630� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'spec_helper' describe '<%= metadata.name %>' do context 'with defaults for all parameters' do it { should contain_class('<%= metadata.name %>') } end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/skeleton/templates/generator/spec/spec_helper.rb����������������0000664�0052762�0001160�00000000064�12650174557�030737� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppetlabs_spec_helper/module_spec_helper' ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/skeleton/templates/generator/tests/�����������������������������0000775�0052762�0001160�00000000000�12650174565�026330� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/skeleton/templates/generator/tests/init.pp.erb������������������0000664�0052762�0001160�00000001012�12650174557�030376� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The baseline for module testing used by Puppet Labs is that each manifest # should have a corresponding test manifest that declares that class or defined # type. # # Tests are then run by using puppet apply --noop (to check for compilation # errors and view a log of events) or by fully applying the test in a virtual # environment (to compare the resulting system state to the desired state). # # Learn more about module testing here: # http://docs.puppetlabs.com/guides/tests_smoke.html # include <%= metadata.name %> ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/tar.rb����������������������������������������������������������0000664�0052762�0001160�00000000662�12650174557�020476� 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::Util.which('tar') && ! Puppet::Util::Platform.windows? Gnu.new elsif Puppet.features.minitar? && Puppet.features.zlib? Mini.new else raise RuntimeError, 'No suitable tar implementation found' end end end ������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/tar/������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020144� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/module_tool/tar/gnu.rb������������������������������������������������������0000664�0052762�0001160�00000001255�12650174557�021266� 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 a-wst {} +") 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-3.8.5/lib/puppet/module_tool/tar/mini.rb�����������������������������������������������������0000664�0052762�0001160�00000003452�12650174557�021432� 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 :file_done File.chmod(0444, "#{destdir}/#{name}") when :dir, :file_start validate_entry(destdir, name) Puppet.debug("Extracting: #{destdir}/#{name}") end end end end def pack(sourcedir, destfile) Zlib::GzipWriter.open(destfile) do |writer| Archive::Tar::Minitar.pack(sourcedir, writer) end end private # 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: http://pic.dhe.ibm.com/infocenter/zos/v1r13/index.jsp?topic=%2Fcom.ibm.zos.r13.bpxa500%2Ftaf.htm # pax format info: http://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.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-3.8.5/lib/puppet/network.rb������������������������������������������������������������������0000664�0052762�0001160�00000000135�12650174557�017052� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Just a stub, so we can correctly scope other classes. module Puppet::Network # :nodoc: end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/��������������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�016525� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/auth_config_parser.rb�����������������������������������������������0000664�0052762�0001160�00000004767�12650174557�022733� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/network/rights' module Puppet::Network class AuthConfigParser def self.new_from_file(file) self.new(File.read(file)) 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? raise Puppet::ConfigurationError, "Missing or invalid 'path' before right directive at line #{count} of #{@file}" end parse_right_directive(right, $1, $2, count) else raise Puppet::ConfigurationError, "Invalid line #{count}: #{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, right| right.valid? } rights end def parse_right_directive(right, var, value, count) value.strip! case var when "allow" modify_right(right, :allow, value, "allowing %s access", count) when "deny" modify_right(right, :deny, value, "denying %s access", count) when "allow_ip" modify_right(right, :allow_ip, value, "allowing IP %s access", count) when "deny_ip" modify_right(right, :deny_ip, value, "denying IP %s access", count) when "method" modify_right(right, :restrict_method, value, "allowing 'method' %s", count) when "environment" modify_right(right, :restrict_environment, value, "adding environment %s", count) when /auth(?:enticated)?/ modify_right(right, :restrict_authenticated, value, "adding authentication %s", count) else raise Puppet::ConfigurationError, "Invalid argument '#{var}' at line #{count}" end end def modify_right(right, method, value, msg, count) value.split(/\s*,\s*/).each do |val| begin val.strip! right.info msg % val right.send(method, val) rescue Puppet::AuthStoreError => detail raise Puppet::ConfigurationError, "#{detail} at line #{count} of #{@file}", detail.backtrace end end end end end ���������puppet-3.8.5/lib/puppet/network/authconfig.rb�������������������������������������������������������0000664�0052762�0001160�00000005531�12650174557�021206� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/network/rights' module Puppet class ConfigurationError < Puppet::Error; end class Network::AuthConfig attr_accessor :rights DEFAULT_ACL = [ { :acl => "~ ^\/catalog\/([^\/]+)$", :method => :find, :allow => '$1', :authenticated => true }, { :acl => "~ ^\/node\/([^\/]+)$", :method => :find, :allow => '$1', :authenticated => true }, # this one will allow all file access, and thus delegate # to fileserver.conf { :acl => "/file" }, { :acl => "/certificate_revocation_list/ca", :method => :find, :authenticated => true }, { :acl => "~ ^\/report\/([^\/]+)$", :method => :save, :allow => '$1', :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 => "/certificate/ca", :method => :find, :authenticated => :any }, { :acl => "/certificate/", :method => :find, :authenticated => :any }, { :acl => "/certificate_request", :method => [:find, :save], :authenticated => :any }, { :acl => "/status", :method => [:find], :authenticated => true }, # API V2.0 { :acl => "/v2.0/environments", :method => :find, :allow => '*', :authenticated => true }, ] # 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 DEFAULT_ACL.each do |acl| unless rights[acl[:acl]] Puppet.info "Inserting default '#{acl[:acl]}' (auth #{acl[:authenticated]}) ACL" 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 explicitely 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}") raise authorization_failure_exception end end def initialize(rights=nil) @rights = rights || Puppet::Network::Rights.new insert_default_acl end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/authentication.rb���������������������������������������������������0000664�0052762�0001160�00000003465�12650174557�022102� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/ssl/certificate_authority' require 'puppet/util/log/rate_limited_logger' # Place for any authentication related bits module Puppet::Network::Authentication # Create a rate-limited logger for the expiration warning that uses the run interval # as the minimum amount of time before a warning about the same cert can be logged again. # This is a class variable so that all classes that include the module share the same logger. @@logger = Puppet::Util::Log::RateLimitedLogger.new(Puppet[:runinterval]) # Check the expiration of known certificates and optionally any that are specified as part of a request def warn_if_near_expiration(*certs) # Check CA cert if we're functioning as a CA certs << Puppet::SSL::CertificateAuthority.instance.host.certificate if Puppet::SSL::CertificateAuthority.ca? # Depending on the run mode, the localhost certificate will be for the # master or the agent. Don't load the certificate if the CA cert is not # present: infinite recursion will occur as another authenticated request # will be spawned to download the CA cert. if [Puppet[:hostcert], Puppet[:localcacert]].all? {|path| Puppet::FileSystem.exist?(path) } certs << Puppet::SSL::Host.localhost.certificate end # Remove nil values for caller convenience certs.compact.each do |cert| # Allow raw OpenSSL certificate instances or Puppet certificate wrappers to be specified cert = Puppet::SSL::Certificate.from_instance(cert) if cert.is_a?(OpenSSL::X509::Certificate) raise ArgumentError, "Invalid certificate '#{cert.inspect}'" unless cert.is_a?(Puppet::SSL::Certificate) if cert.near_expiration? @@logger.warning("Certificate '#{cert.unmunged_name}' will expire on #{cert.expiration.strftime('%Y-%m-%dT%H:%M:%S%Z')}") end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/authorization.rb����������������������������������������������������0000664�0052762�0001160�00000001717�12650174557�021761� 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 def authconfig 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-3.8.5/lib/puppet/network/authstore.rb��������������������������������������������������������0000664�0052762�0001160�00000021335�12650174557�021075� 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 weirdnesses 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}" 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}" 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}" 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-3.8.5/lib/puppet/network/client_request.rb���������������������������������������������������0000664�0052762�0001160�00000001464�12650174557�022106� 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-3.8.5/lib/puppet/network/format.rb�����������������������������������������������������������0000664�0052762�0001160�00000006563�12650174557�020355� 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 def init_attribute(name, default) if value = @options[name] @options.delete(name) else value = default end 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) method_list.each do |method, value| init_attribute(method, value) end raise ArgumentError, "Unsupported option(s) #{@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, "#{instances[0].class} does not respond to #{render_multiple_method}; can not intern multiple instances to #{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-3.8.5/lib/puppet/network/format_handler.rb���������������������������������������������������0000664�0052762�0001160�00000005336�12650174557�022047� 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 raise ArgumentError, "No format match the given format name or mime-type (#{format})" if out.nil? 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 [Puppet::Network::Format, nil] the most suitable format # @api private def self.most_suitable_format_for(accepted, supported) format_name = accepted.collect do |accepted| accepted.to_s.sub(/;q=.*$/, '') end.collect do |accepted| if accepted == ALL_MEDIA_TYPES supported.first else format_to_canonical_name_or_nil(accepted) end end.find do |accepted| supported.include?(accepted) end if format_name format_for(format_name) 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-3.8.5/lib/puppet/network/format_support.rb���������������������������������������������������0000664�0052762�0001160�00000006650�12650174557�022146� 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 raise Puppet::Network::FormatHandler::FormatError, "Could not intern from #{format}: #{err}", err.backtrace end def convert_from_multiple(format, data) get_format(format).intern_multiple(self, data) rescue => err raise Puppet::Network::FormatHandler::FormatError, "Could not intern_multiple from #{format}: #{err}", err.backtrace end def render_multiple(format, instances) get_format(format).render_multiple(instances) rescue => err raise Puppet::Network::FormatHandler::FormatError, "Could not render_multiple to #{format}: #{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 def to_pson(*args) to_data_hash.to_pson(*args) end def render(format = nil) format ||= self.class.default_format self.class.get_format(format).render(self) rescue => err raise Puppet::Network::FormatHandler::FormatError, "Could not render to #{format}: #{err}", err.backtrace end def mime(format = nil) format ||= self.class.default_format self.class.get_format(format).mime rescue => err raise Puppet::Network::FormatHandler::FormatError, "Could not mime to #{format}: #{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-3.8.5/lib/puppet/network/formats.rb����������������������������������������������������������0000664�0052762�0001160�00000013523�12650174557�020532� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/network/format_handler' 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, :safe => true, :deserialize_symbols => true) data_to_instance(klass, data) end def intern_multiple(klass, text) data = YAML.load(text, :safe => true, :deserialize_symbols => true) 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}" 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 # This is a "special" format which is used for the moment only when sending facts # as REST GET parameters (see Puppet::Configurer::FactHandler). # This format combines a yaml serialization, then zlib compression and base64 encoding. Puppet::Network::FormatHandler.create_serialized_formats(:b64_zlib_yaml) do require 'base64' def use_zlib? Puppet.features.zlib? && Puppet[:zlib] end def requiring_zlib if use_zlib? yield else raise Puppet::Error, "the zlib library is not installed or is disabled." end end def intern(klass, text) requiring_zlib do Puppet::Network::FormatHandler.format(:yaml).intern(klass, decode(text)) end end def intern_multiple(klass, text) requiring_zlib do Puppet::Network::FormatHandler.format(:yaml).intern_multiple(klass, decode(text)) end end def render(instance) encode(instance.to_yaml) end def render_multiple(instances) encode(instances.to_yaml) end def supported?(klass) true end def decode(data) Zlib::Inflate.inflate(Base64.decode64(data)) end def encode(text) requiring_zlib do Base64.encode64(Zlib::Deflate.deflate(text, Zlib::BEST_COMPRESSION)) end end end Puppet::Network::FormatHandler.create(:s, :mime => "text/plain", :extension => "txt") # A very low-weight format so it'll never get chosen automatically. Puppet::Network::FormatHandler.create(:raw, :mime => "application/x-raw", :weight => 1) do def intern_multiple(klass, text) raise NotImplementedError end def render_multiple(instances) raise NotImplementedError end # LAK:NOTE The format system isn't currently flexible enough to handle # what I need to support raw formats just for individual instances (rather # than both individual and collections), but we don't yet have enough data # to make a "correct" design. # So, we hack it so it works for singular but fail if someone tries it # on plurals. def supported?(klass) true end end 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. By default, # we'll include class information but we won't rely on it - we don't # want class names to be required because we then can't change our # internal class names, which is bad. 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 # 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(:pson) end def render(datum) # String to String return datum if datum.is_a? String return datum if datum.is_a? Numeric # Simple hash to table if datum.is_a? Hash and datum.keys.all? { |x| x.is_a? String or 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. return json.render(datum) end def render_multiple(data) data.collect(&:render).join("\n") end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/http.rb�������������������������������������������������������������0000664�0052762�0001160�00000001325�12650174557�020033� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Network::HTTP HEADER_ENABLE_PROFILING = "X-Puppet-Profiling" HEADER_PUPPET_VERSION = "X-Puppet-Version" 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/v1' require 'puppet/network/http/api/v2' 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' end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/http/���������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017504� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/http/api.rb���������������������������������������������������������0000664�0052762�0001160�00000000045�12650174557�020602� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Network::HTTP::API end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/http/api/�����������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020255� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/http/api/v1.rb������������������������������������������������������0000664�0052762�0001160�00000020022�12650174557�021125� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/network/authorization' class Puppet::Network::HTTP::API::V1 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 } } def self.routes Puppet::Network::HTTP::Route.path(/.*/).any(new) end # handle an HTTP request def call(request, response) indirection_name, method, key, params = uri2indirection(request.method, request.path, request.params) certificate = request.client_cert check_authorization(method, "/#{indirection_name}/#{key}", params) indirection = Puppet::Indirector::Indirection.instance(indirection_name.to_sym) raise ArgumentError, "Could not find indirection '#{indirection_name}'" unless indirection 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.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 rescue Puppet::Network::HTTP::Error::HTTPError => e return do_http_control_exception(response, e) rescue Exception => e return do_exception(response, e) end def uri2indirection(http_method, uri, params) environment, indirection, key = uri.split("/", 4)[1..-1] # the first field is always nil because of the leading slash raise ArgumentError, "The environment must be purely alphanumeric, not '#{environment}'" unless Puppet::Node::Environment.valid_name?(environment) raise ArgumentError, "The indirection name must be purely alphanumeric, not '#{indirection}'" unless indirection =~ /^\w+$/ method = indirection_method(http_method, indirection) configured_environment = Puppet.lookup(:environments).get(environment) if configured_environment.nil? raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new("Could not find environment '#{environment}'", Puppet::Network::HTTP::Issues::ENVIRONMENT_NOT_FOUND) else configured_environment = configured_environment.override_from_commandline(Puppet.settings) params[:environment] = configured_environment end params.delete(:bucket_path) raise ArgumentError, "No request key specified in #{uri}" if key == "" or key.nil? key = URI.unescape(key) [indirection, method, key, params] end private def do_http_control_exception(response, exception) msg = exception.message Puppet.info(msg) response.respond_with(exception.status, "text/plain", msg) end def do_exception(response, exception, status=400) if exception.is_a?(Puppet::Network::AuthorizationError) # make sure we return the correct status code # for authorization issues status = 403 if status == 400 end Puppet.log_exception(exception) response.respond_with(status, "text/plain", exception.to_s) end # 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 #{indirection.name} #{key}", Puppet::Network::HTTP::Issues::RESOURCE_NOT_FOUND) end format = accepted_response_formatter_for(indirection.model, request) rendered_result = result if result.respond_to?(:render) Puppet::Util::Profiler.profile("Rendered result in #{format}", [:http, :v1_render, format]) do rendered_result = result.render(format) end end Puppet::Util::Profiler.profile("Sent response", [:http, :v1_response]) do response.respond_with(200, 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.name} #{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.name} with '#{key}'", Puppet::Network::HTTP::Issues::RESOURCE_NOT_FOUND) end format = accepted_response_formatter_for(indirection.model, request) response.respond_with(200, format, indirection.model.render_multiple(format, result)) end # Execute our destroy. def do_destroy(indirection, key, params, request, response) formatter = accepted_response_formatter_or_yaml_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_yaml_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 def accepted_response_formatter_for(model_class, request) accepted_formats = request.headers['accept'] or raise Puppet::Network::HTTP::Error::HTTPNotAcceptableError.new("Missing required Accept header", Puppet::Network::HTTP::Issues::MISSING_HEADER_FIELD) request.response_formatter_for(model_class.supported_formats, accepted_formats) end def accepted_response_formatter_or_yaml_for(model_class, request) accepted_formats = request.headers['accept'] || "yaml" request.response_formatter_for(model_class.supported_formats, accepted_formats) end def read_body_into_model(model_class, request) data = request.body.to_s format = request.format model_class.convert_from(format, data) end def indirection_method(http_method, indirection) raise ArgumentError, "No support for http method #{http_method}" unless METHOD_MAP[http_method] unless method = METHOD_MAP[http_method][plurality(indirection)] raise ArgumentError, "No support for plurality #{plurality(indirection)} for #{http_method} operations" end method end def self.indirection2uri(request) indirection = request.method == :search ? pluralize(request.indirection_name.to_s) : request.indirection_name.to_s "/#{request.environment.to_s}/#{indirection}/#{request.escaped_key}#{request.query_string}" end def self.request_to_uri_and_body(request) indirection = request.method == :search ? pluralize(request.indirection_name.to_s) : request.indirection_name.to_s ["/#{request.environment.to_s}/#{indirection}/#{request.escaped_key}", request.query_string.sub(/^\?/,'')] end def self.pluralize(indirection) return(indirection == "status" ? "statuses" : indirection + "s") end def plurality(indirection) # NOTE This specific hook for facts is 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" return :plural if indirection == "inventory" result = (indirection =~ /s$|_search$/) ? :plural : :singular indirection.sub!(/s$|_search$/, '') indirection.sub!(/statuse$/, 'status') result end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/http/api/v2.rb������������������������������������������������������0000664�0052762�0001160�00000001443�12650174557�021134� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Network::HTTP::API::V2 require 'puppet/network/http/api/v2/environments' require 'puppet/network/http/api/v2/authorization' def self.routes path(%r{^/v2\.0}). get(Authorization.new). chain(ENVIRONMENTS, NOT_FOUND) end private def self.path(path) Puppet::Network::HTTP::Route.path(path) end def self.provide(&block) lambda do |request, response| block.call.call(request, response) end end NOT_FOUND = Puppet::Network::HTTP::Route. path(/.*/). any(lambda do |req, res| raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new(req.path, Puppet::Network::HTTP::Issues::HANDLER_NOT_FOUND) end) ENVIRONMENTS = path(%r{^/environments$}).get(provide do Environments.new(Puppet.lookup(:environments)) end) end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/http/api/v2/��������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020604� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/http/api/v2/authorization.rb����������������������������������������0000664�0052762�0001160�00000001145�12650174557�024033� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Network::HTTP::API::V2::Authorization include Puppet::Network::Authorization def call(request, response) if request.method != "GET" raise Puppet::Network::HTTP::Error::HTTPNotAuthorizedError.new("Only GET requests are authorized for V2 endpoints", Puppet::Network::HTTP::Issues::UNSUPPORTED_METHOD) end begin 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 end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/http/api/v2/environments.rb�����������������������������������������0000664�0052762�0001160�00000001456�12650174557�023667� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'json' class Puppet::Network::HTTP::API::V2::Environments def initialize(env_loader) @env_loader = env_loader end def call(request, response) response.respond_with(200, "application/json", 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 == 1.0 / 0.0 # INFINITY "unlimited" else ttl end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/http/compression.rb�������������������������������������������������0000664�0052762�0001160�00000006260�12650174557�022377� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/network/http' module Puppet::Network::HTTP::Compression # 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' return Zlib::GzipReader.new(StringIO.new(response.body)).read when 'deflate' return Zlib::Inflate.new.inflate(response.body) when nil, 'identity' return response.body else raise Net::HTTPError.new("Unknown content encoding - #{response['content-encoding']}", response) end end def uncompress(response) raise Net::HTTPError.new("No block passed") 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 - #{response['content-encoding']}", response) end yield uncompressor uncompressor.close end def add_accept_encoding(headers={}) if Puppet.settings[:http_compression] headers['accept-encoding'] = 'gzip; q=1.0, deflate; q=1.0; identity' else headers['accept-encoding'] = 'identity' end headers end # This adapters knows how to uncompress both 'zlib' stream (the deflate algorithm from Content-Encoding) # and GZip streams. class ZlibAdapter def initialize # 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 = Zlib::Inflate.new(15 + 32) @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 @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-3.8.5/lib/puppet/network/http/connection.rb��������������������������������������������������0000664�0052762�0001160�00000017724�12650174557�022204� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'net/https' require 'puppet/ssl/host' require 'puppet/ssl/configuration' require 'puppet/ssl/validator' require 'puppet/network/authentication' require 'puppet/network/http' require 'uri' 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 include Puppet::Network::Authentication 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 [Fixnum] 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 [Fixnum] :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): #{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 # @option options [Hash{Symbol => String}] :basic_auth The basic auth # :username and :password to use for the request # @param path [String] # @param headers [Hash{String => String}] # @!macro common_options # @api public def get(path, headers = {}, options = {}) request_with_redirects(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 request_with_redirects(request, options) end # @param path [String] # @param headers [Hash{String => String}] # @!macro common_options # @api public def head(path, headers = {}, options = {}) request_with_redirects(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 = {}) request_with_redirects(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 request_with_redirects(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 request_with_redirects(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) if [301, 302, 307].include?(current_response.code.to_i) # handle the 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 else response = current_response end end # and try again... end raise RedirectionLimitExceededException, "Too many HTTP redirections for #{@host}:#{@port}" end def apply_options_to(request, options) if options[:basic_auth] request.basic_auth(options[:basic_auth][:user], options[:basic_auth][:password]) end end def execute_request(connection, request) response = connection.request(request) # Check the peer certs and warn if they're nearing expiration. warn_if_near_expiration(*@verify.peer_certs) response 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 if error.message.include? "certificate verify failed" msg = error.message msg << ": [" + @verify.verify_errors.join('; ') + "]" raise Puppet::Error, msg, error.backtrace elsif error.message =~ /hostname.*not match.*server certificate/ leaf_ssl_cert = @verify.peer_certs.last valid_certnames = [leaf_ssl_cert.name, *leaf_ssl_cert.subject_alt_names].uniq msg = valid_certnames.length > 1 ? "one of #{valid_certnames.join(', ')}" : valid_certnames.first msg = "Server hostname '#{site.host}' did not match server certificate; expected #{msg}" raise Puppet::Error, msg, error.backtrace else raise end end end end ��������������������������������������������puppet-3.8.5/lib/puppet/network/http/error.rb�������������������������������������������������������0000664�0052762�0001160�00000003347�12650174557�021172� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require '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 JSON({: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, CODE, issue_kind) end end class HTTPNotFoundError < HTTPError CODE = 404 def initialize(message, issue_kind = Issues::RUNTIME_ERROR) super("Not Found: " + message, CODE, issue_kind) end end class HTTPNotAuthorizedError < HTTPError CODE = 403 def initialize(message, issue_kind = Issues::RUNTIME_ERROR) super("Not Authorized: " + message, CODE, issue_kind) end end class HTTPBadRequestError < HTTPError CODE = 400 def initialize(message, issue_kind = Issues::RUNTIME_ERROR) super("Bad Request: " + message, CODE, issue_kind) end end class HTTPMethodNotAllowedError < HTTPError CODE = 405 def initialize(message, issue_kind = Issues::RUNTIME_ERROR) super("Method Not Allowed: " + message, CODE, issue_kind) end end class HTTPServerError < HTTPError CODE = 500 attr_reader :backtrace def initialize(original_error, issue_kind = Issues::RUNTIME_ERROR) super("Server Error: " + original_error.message, CODE, issue_kind) @backtrace = original_error.backtrace end def to_json JSON({:message => message, :issue_kind => @issue_kind, :stacktrace => self.backtrace}) end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/http/factory.rb�����������������������������������������������������0000664�0052762�0001160�00000002104�12650174557�021476� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'openssl' require 'net/http' # 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 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] if Puppet[:http_proxy_host] == "none" args << nil << nil else args << Puppet[:http_proxy_host] << Puppet[:http_proxy_port] end http = Net::HTTP.new(*args) http.use_ssl = site.use_ssl? # Use configured timeout (#1176) http.read_timeout = Puppet[:configtimeout] http.open_timeout = Puppet[:configtimeout] if Puppet[:http_debug] http.set_debug_output($stderr) end http end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/http/handler.rb�����������������������������������������������������0000664�0052762�0001160�00000012476�12650174557�021461� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Network::HTTP end require 'puppet/network/http' require 'puppet/network/http/api/v1' require 'puppet/network/authentication' require 'puppet/network/rights' require 'puppet/util/profiler' require 'puppet/util/profiler/aggregate' require 'resolv' module Puppet::Network::HTTP::Handler include Puppet::Network::Authentication 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: #{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 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}", [:http, request_method, request_path]) do if route = @routes.find { |route| route.matches?(new_request) } route.process(new_request, new_response) else raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new("No route for #{new_request.method} #{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 Exception => e http_e = Puppet::Network::HTTP::Error::HTTPServerError.new(e) Puppet.err(http_e.message) 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 #{result[:ip]}: #{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) case value when /^---/ Puppet.debug("Found YAML while processing request parameter #{param} (value: <#{value}>)") Puppet.deprecation_warning("YAML in network requests is deprecated and will be removed in a future version. See http://links.puppetlabs.com/deprecate_yaml_on_network") YAML.load(value, :safe => true, :deserialize_symbols => true) when 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(:debug), request_params.object_id)) end end def remove_profiler(profiler) profiler.shutdown Puppet::Util::Profiler.remove_profiler(profiler) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/http/issues.rb������������������������������������������������������0000664�0052762�0001160�00000000706�12650174557�021350� 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 end ����������������������������������������������������������puppet-3.8.5/lib/puppet/network/http/memory_response.rb���������������������������������������������0000664�0052762�0001160�00000000331�12650174557�023255� 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-3.8.5/lib/puppet/network/http/nocache_pool.rb������������������������������������������������0000664�0052762�0001160�00000000741�12650174557�022465� 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-3.8.5/lib/puppet/network/http/pool.rb��������������������������������������������������������0000664�0052762�0001160�00000005566�12650174557�021017� 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}") 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 http end 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-3.8.5/lib/puppet/network/http/rack.rb��������������������������������������������������������0000664�0052762�0001160�00000002205�12650174557�020751� 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: "%s"' % detail.message # log what happened Puppet.log_exception(detail, "Puppet Server (Rack): Internal Server Error: Unhandled Exception: \"%s\"" % detail.message) end response.finish end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/http/rack/����������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020424� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/http/rack/rest.rb���������������������������������������������������0000664�0052762�0001160�00000007025�12650174557�021733� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'openssl' require 'cgi' require 'puppet/network/http/handler' require 'puppet/util/ssl' 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::V2.routes, Puppet::Network::HTTP::API::V1.routes]) end def set_content_type(response, format) response[ContentType] = format_to_mime(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) request.path 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? nil else cert = Puppet::SSL::Certificate.from_instance(OpenSSL::X509::Certificate.new(cert)) warn_if_near_expiration(cert) 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-3.8.5/lib/puppet/network/http/request.rb�����������������������������������������������������0000664�0052762�0001160�00000003751�12650174557�021530� 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: #{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 format if header = headers['content-type'] header.gsub!(/\s*;.*$/,'') # strip any charset format = Puppet::Network::FormatHandler.mime(header) if format.nil? raise "Client sent a mime-type (#{header}) that doesn't correspond to a format we support" else report_if_deprecated(format) return format.name.to_s if format.suitable? end end raise "No Content-Type header was received, it isn't possible to unserialize the request" end def response_formatter_for(supported_formats, accepted_formats = headers['accept']) formatter = Puppet::Network::FormatHandler.most_suitable_format_for( accepted_formats.split(/\s*,\s*/), supported_formats) if formatter.nil? raise Puppet::Network::HTTP::Error::HTTPNotAcceptableError.new("No supported formats are acceptable (Accept: #{accepted_formats})", Puppet::Network::HTTP::Issues::UNSUPPORTED_FORMAT) end report_if_deprecated(formatter) formatter end def report_if_deprecated(format) if format.name == :yaml || format.name == :b64_zlib_yaml Puppet.deprecation_warning("YAML in network requests is deprecated and will be removed in a future version. See http://links.puppetlabs.com/deprecate_yaml_on_network") end end end �����������������������puppet-3.8.5/lib/puppet/network/http/response.rb����������������������������������������������������0000664�0052762�0001160�00000000420�12650174557�021664� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Network::HTTP::Response def initialize(handler, response) @handler = handler @response = response end def respond_with(code, type, body) @handler.set_content_type(@response, type) @handler.set_response(@response, body, code) end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/http/route.rb�������������������������������������������������������0000664�0052762�0001160�00000004215�12650174557�021172� 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-3.8.5/lib/puppet/network/http/session.rb�����������������������������������������������������0000664�0052762�0001160�00000000562�12650174557�021520� 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-3.8.5/lib/puppet/network/http/site.rb��������������������������������������������������������0000664�0052762�0001160�00000001423�12650174557�020776� 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-3.8.5/lib/puppet/network/http/webrick.rb�����������������������������������������������������0000664�0052762�0001160�00000007255�12650174557�021471� 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.listeners.each { |l| l.start_immediately = false } @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 %.1f seconds of connecting" % 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) 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) if Puppet.run_mode.master? file = Puppet[:masterhttplog] else file = Puppet[:httplog] end # open the log manually to prevent file descriptor leak file_io = ::File.open(file, "a+") 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.name} and not running on a valid certificate authority" unless host.certificate results[:SSLPrivateKey] = host.key.content results[:SSLCertificate] = host.certificate.content results[:SSLStartImmediately] = true 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_chain_file => Puppet[:ssl_server_ca_chain], :ca_auth_file => Puppet[:ssl_server_ca_auth]) end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/http/webrick/�������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�021132� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/http/webrick/rest.rb������������������������������������������������0000664�0052762�0001160�00000005106�12650174557�022437� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/network/http/handler' require 'resolv' require 'webrick' 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::V2.routes, Puppet::Network::HTTP::API::V1.routes]) super(server) end # Retrieve the request parameters, including authentication information. def params(request) params = request.query || {} 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 cert = Puppet::SSL::Certificate.from_instance(cert) warn_if_near_expiration(cert) 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_to_mime(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 if RUBY_VERSION[0,3] == "1.8" response["connection"] = 'close' 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-3.8.5/lib/puppet/network/http_pool.rb��������������������������������������������������������0000664�0052762�0001160�00000004170�12650174557�021065� 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-3.8.5/lib/puppet/network/resolver.rb���������������������������������������������������������0000664�0052762�0001160�00000005474�12650174557�020726� 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, records| while next_rr = records.delete(find_weighted_server(records)) 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 private 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 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-3.8.5/lib/puppet/network/rest_controller.rb��������������������������������������������������0000664�0052762�0001160�00000000064�12650174557�022273� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Network::RESTController # :nodoc: end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/network/rights.rb�����������������������������������������������������������0000664�0052762�0001160�00000013442�12650174557�020357� 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 |method| is_forbidden_and_why?(path, params.merge({:method => method})) 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 right = @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 host_description = args[:node] ? "#{args[:node]}(#{args[:ip]})" : args[:ip] msg = "#{host_description} access to #{name} [#{args[:method]}]" if args[:authenticated] msg += " authenticated " end if right msg += " at #{right.file}:#{right.line}" end AuthorizationError.new("Forbidden request: #{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}'" 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" 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" 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" 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}" 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-3.8.5/lib/puppet/network/server.rb�����������������������������������������������������������0000664�0052762�0001160�00000001457�12650174557�020370� 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-3.8.5/lib/puppet/node.rb���������������������������������������������������������������������0000664�0052762�0001160�00000012160�12650174557�016307� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require '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 # 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 ::PSON.register_document_type('Node',self) ENVIRONMENT = 'environment'.freeze def self.from_data_hash(data) raise ArgumentError, "No name provided in serialized data" unless name = data['name'] node = new(name) node.classes = data['classes'] node.parameters = data['parameters'] node.environment_name = data['environment'] node end def self.from_pson(pson) Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.") self.from_data_hash(pson) end def to_data_hash result = { 'name' => name, 'environment' => environment.name, } result['classes'] = classes unless classes.empty? result['parameters'] = parameters unless parameters.empty? result end def to_pson_data_hash(*args) { 'document_type' => "Node", 'data' => to_data_hash, } end def to_pson(*args) to_pson_data_hash.to_pson(*args) 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 # Tt 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? @parameters[ENVIRONMENT] = @environment.name if @parameters.include?(ENVIRONMENT) 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] if env = options[:environment] self.environment = env end @time = Time.now end # Merge the node facts with parameters from the node source. def fact_merge if @facts = Puppet::Node::Facts.indirection.find(name, :environment => environment) @facts.sanitize merge(@facts.values) end rescue => detail error = Puppet::Error.new("Could not retrieve facts for #{name}: #{detail}") error.set_backtrace(detail.backtrace) raise error end # Merge any random parameters into our parameter list. def merge(params) params.each do |name, value| @parameters[name] = value unless @parameters.include?(name) end @parameters[ENVIRONMENT] ||= self.environment.name.to_s 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}" 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}") unless @trusted_data.nil? @trusted_data = data.freeze end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/node/�����������������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�015761� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/node/environment.rb���������������������������������������������������������0000664�0052762�0001160�00000047702�12650174557�020665� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util' require 'puppet/util/cacher' 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 include Puppet::Util::Cacher NO_MANIFEST = :no_manifest # @api private def self.seen @seen ||= {} end # Create a new environment with the given name, or return an existing one # # The environment class memoizes instances so that attempts to instantiate an # environment with the same name with an existing environment will return the # existing environment. # # @overload self.new(environment) # @param environment [Puppet::Node::Environment] # @return [Puppet::Node::Environment] the environment passed as the param, # this is implemented so that a calling class can use strings or # environments interchangeably. # # @overload self.new(string) # @param string [String, Symbol] # @return [Puppet::Node::Environment] An existing environment if it exists, # else a new environment with that name # # @overload self.new() # @return [Puppet::Node::Environment] The environment as set by # Puppet.settings[:environment] # # @api public def self.new(name = nil) return name if name.is_a?(self) name ||= Puppet.settings.value(:environment) raise ArgumentError, "Environment name must be specified" unless name symbol = name.to_sym return seen[symbol] if seen[symbol] obj = self.create(symbol, split_path(Puppet.settings.value(:modulepath, symbol)), Puppet.settings.value(:manifest, symbol), Puppet.settings.value(:config_version, symbol)) seen[symbol] = obj end # Create a new environment with the given name # # @param name [Symbol] the name of the # @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, expand_dirs(extralibs() + modulepath), manifest == NO_MANIFEST ? manifest : File.expand_path(manifest), config_version) obj 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) create(name, [], NO_MANIFEST) end # Instantiate a new environment # # @note {Puppet::Node::Environment.new} is overridden to return memoized # objects, 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 # set watching to true for legacy environments - the directory based environment loaders will set this to # false for directory based environments after the environment has been created. @watching = true end # Returns if files are being watched or not. # @api private # def watching? @watching end # Turns watching of files on or off # @param flag [TrueClass, FalseClass] if files should be watched or not # @ api private def watching=(flag) @watching = flag 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 manfiest # 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) || (settings.set_by_cli?(:manifestdir) && settings.value(:manifest).start_with?(settings.value(:manifestdir))) overrides[:manifest] = settings.value(:manifest) end overrides.empty? ? self : self.override_with(overrides) end # Retrieve the environment for the current process. # # @note This should only used when a catalog is being compiled. # # @api private # # @return [Puppet::Node::Environment] the currently set environment if one # has been explicitly set, else it will return the '*root*' environment def self.current Puppet.deprecation_warning("Puppet::Node::Environment.current has been replaced by Puppet.lookup(:current_environment), see http://links.puppetlabs.com/current-env-deprecation") Puppet.lookup(:current_environment) 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 # Clear all memoized environments and the 'current' environment # # @api private def self.clear seen.clear 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 # 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 public def conflicting_manifest_settings? return false if Puppet[:environmentpath].empty? || !Puppet[:disable_per_environment_manifest] environment_conf = Puppet.lookup(:environments).get_conf(name) original_manifest = environment_conf.raw_setting(:manifest) !original_manifest.nil? && !original_manifest.empty? && original_manifest != Puppet[:default_manifest] end # @api private def future_parser? if @future_parser.nil? environment_conf = Puppet.lookup(:environments).get_conf(name) @future_parser = 'future' == (environment_conf.nil? ? Puppet[:parser] : environment_conf.parser) end @future_parser 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 # @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) author, modname = forge_name.split('/') found_mod = self.module(modname) found_mod and found_mod.forge_name == forge_name ? found_mod : nil end # @!attribute [r] modules # 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]`. # @see Puppet::Util::Cacher.cached_attr # @api public # @return [Array<Puppet::Module>] All modules for this environment cached_attr(:modules, Puppet[:filetimeout]) do module_references = [] seen_modules = {} modulepath.each do |path| Dir.entries(path).each do |name| warn_about_mistaken_path(path, name) next if module_references.include?(name) if not seen_modules[name] module_references << {:name => name, :path => File.join(path, name)} seen_modules[name] = true end end end module_references.collect do |reference| begin Puppet::Module.new(reference[:name], reference[:path], self) rescue Puppet::Module::Error nil end end.compact 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| Dir.chdir(path) do module_names = Dir.glob('*').select do |d| FileTest.directory?(d) && (File.basename(d) =~ /\A\w+(-\w+)*\Z/) end modules_by_path[path] = module_names.sort.map do |name| Puppet::Module.new(name, File.join(path, name), self) end 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 # Set a periodic watcher on the file, so we can tell if it has changed. # If watching has been turned off, this call has no effect. # @param file[File,String] File instance or filename # @api private def watch_file(file) if watching? known_resource_types.watch_file(file.to_s) end end # Checks if a reparse is required (cache of files is stale). # This call does nothing unless files are being watched. # def check_for_reparse if (Puppet[:code] != @parsed_code) || (watching? && @known_resource_types && @known_resource_types.require_reparse?) @parsed_code = nil @known_resource_types = nil end end # @return [String] The stringified value of the `name` instance variable # @api public def to_s name.to_s 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 # Return only the environment name when serializing. # # The only thing we care about when serializing an environment is its # identity; everything else is ephemeral and should not be stored or # transmitted. # # @api public def to_zaml(z) self.to_s.to_zaml(z) 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 ENV["PUPPETLIB"] split_path(ENV["PUPPETLIB"]) else [] end end def self.expand_dirs(dirs) dirs.collect do |dir| File.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.settings[: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 `Puppet.settings[:manifest]`: Puppet will try to load # the environment manifest. By default this is `$manifestdir/site.pp` # # @note This method will return an empty hostclass if # `Puppet.settings[:ignoreimport]` is set to true. # # @return [Puppet::Parser::AST::Hostclass] The AST hostclass object # representing the 'main' hostclass def perform_initial_import return empty_parse_result if Puppet[:ignoreimport] parser = Puppet::Parser::ParserFactory.parser(self) @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 Puppet::Parser::AST::Hostclass.new('') elsif File.directory?(file) if Puppet.future_parser? 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 else parse_results = Dir.entries(file).find_all { |f| f =~ /\.pp$/ }.sort.map do |file_to_parse| parser.file = File.join(file, file_to_parse) parser.parse end 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 #{self}: #{detail}" error = Puppet::Error.new(msg) error.set_backtrace(detail.backtrace) raise error end # Return an empty toplevel hostclass to indicate that no file was loaded # # This is used as the return value of {#perform_initial_import} when # `Puppet.settings[:ignoreimport]` is true. # # @return [Puppet::Parser::AST::Hostclass] def empty_parse_result return Puppet::Parser::AST::Hostclass.new('') end # A special "null" environment # # This environment should be used when there is no specific environment in # effect. NONE = create(:none, []) end ��������������������������������������������������������������puppet-3.8.5/lib/puppet/node/facts.rb���������������������������������������������������������������0000664�0052762�0001160�00000006752�12650174557�017421� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'time' require 'puppet/node' require 'puppet/indirector' require 'puppet/util/pson' # Manage a given node's facts. This either accepts facts and stores them, or # returns facts for a given node. class Puppet::Node::Facts # Set up indirection, so that nodes can be looked for in # the node sources. extend Puppet::Indirector extend Puppet::Util::Pson # 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 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 timestamp = data['values']['_timestamp'] @values.delete_if do |key, val| key =~ /^_/ end #Timestamp will be here in pson timestamp ||= data['timestamp'] timestamp = Time.parse(timestamp) if timestamp.is_a? String self.timestamp = timestamp self.expiration = data['expiration'] if expiration.is_a? String self.expiration = Time.parse(expiration) end end # Convert all fact values into strings. def stringify values.each do |fact, value| values[fact] = value.to_s end 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 strip_internal == other.send(:strip_internal) end def self.from_data_hash(data) new_facts = allocate new_facts.initialize_from_hash(data) new_facts end def self.from_pson(data) Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.") self.from_data_hash(data) end def to_data_hash result = { 'name' => name, 'values' => strip_internal, } 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 # Add internal data to the facts for storage. def add_timestamp self.timestamp = Time.now end def timestamp=(time) self.values['_timestamp'] = time end def timestamp self.values['_timestamp'] end # Strip out that internal data. def strip_internal newvals = values.dup newvals.find_all { |name, value| name.to_s =~ /^_/ }.each { |name, value| newvals.delete(name) } newvals 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-3.8.5/lib/puppet/parameter.rb����������������������������������������������������������������0000664�0052762�0001160�00000052217�12650174557�017351� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require '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 # Unused? # @todo The term "validater" only appears in this location in the Puppet code base. There is `validate` # which seems to works fine without this attribute declaration. # @api private # attr_reader :validater # Unused? # @todo The term "munger" only appears in this location in the Puppet code base. There is munge and unmunge # and they seem to work perfectly fine without this attribute declaration. # @api private # attr_reader :munger # @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<Symbol>] 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 f = self.required_features @doc << "\n\nRequires features #{f.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 # 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 #{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 heirarchy # (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.inspect} in class #{self.name}: #{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 #{self.name}: #{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 arameter. # @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<Symbol>] 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 minimun, 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 # Produces a String with the value formatted for display to a human. # When the parameter value is a: # # * **single valued parameter value** the result is produced on the # form `'value'` where _value_ is the string form of the parameter's value. # # * **Array** the list of values is enclosed in `[]`, and # each produced value is separated by a comma. # # * **Hash** value is output with keys in sorted order enclosed in `{}` with each entry formatted # on the form `'k' => v` where # `k` is the key in string form and _v_ is the value of the key. Entries are comma separated. # # For both Array and Hash this method is called recursively to format contained values. # @note this method does not protect against infinite structures. # # @return [String] The formatted value in string form. # def self.format_value_for_display(value) if value.is_a? Array formatted_values = value.collect {|value| format_value_for_display(value)}.join(', ') "[#{formatted_values}]" elsif value.is_a? Hash # Sorting the hash keys for display is largely for having stable # output to test against, but also helps when scanning for hash # keys, since they will be in ASCIIbetical order. hash = value.keys.sort {|a,b| a.to_s <=> b.to_s}.collect do |k| "'#{k}' => #{format_value_for_display(value[k])}" end.join(', ') "{#{hash}}" else "'#{value}'" end 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-3.8.5/lib/puppet/parameter/������������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017014� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parameter/boolean.rb��������������������������������������������������������0000664�0052762�0001160�00000000614�12650174557�020762� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/coercion' # This specialized {Puppet::Parameter} handles boolean options, accepting lots # of strings and symbols for both truthiness 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-3.8.5/lib/puppet/parameter/package_options.rb������������������������������������������������0000664�0052762�0001160�00000001462�12650174557�022513� 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-3.8.5/lib/puppet/parameter/path.rb�����������������������������������������������������������0000664�0052762�0001160�00000004140�12650174557�020275� 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" end fail("#{name} must be a fully qualified path") 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" end paths end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parameter/value.rb����������������������������������������������������������0000664�0052762�0001160�00000004545�12650174557�020466� 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, :call, :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 additonal 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 = [] @call = :instead 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-3.8.5/lib/puppet/parameter/value_collection.rb�����������������������������������������������0000664�0052762�0001160�00000015747�12650174557�022707� 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 #{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] :call When to call any associated block. The default value is `:instead` which # means that the block should be called instead of the provider. In earlier versions (before 20081031) it # was possible to specify a value of `:before` or `:after` for the purpose of calling # both the block and the provider. Use of these deprecated options will now raise an exception later # in the process when the _is_ value is set (see Puppet::Property#set). # @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) 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? value.block = block else value.call = options[:call] || :none end value.method ||= "set_#{value.name}" if block_given? and ! value.regex? 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.inspect}. " str += "Valid values are #{values.join(", ")}. " unless values.empty? str += "Valid values match #{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 valud 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-3.8.5/lib/puppet/parser.rb�������������������������������������������������������������������0000664�0052762�0001160�00000000225�12650174557�016655� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# @api public module Puppet::Parser; end require 'puppet/parser/parser' require 'puppet/parser/compiler' require 'puppet/resource/type_collection' ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/���������������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�016330� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast.rb���������������������������������������������������������������0000664�0052762�0001160�00000010151�12650174557�017443� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# the parent class for all of our syntactical objects require 'puppet' require 'puppet/util/autoload' # The base class for all of the objects that make up the parse trees. # 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 # Do this so I don't have to type the full path in all of the subclasses AST = Puppet::Parser::AST include Puppet::Util::Errors include Puppet::Util::MethodHelper include Puppet::Util::Docs attr_accessor :parent, :scope, :file, :line, :pos def inspect "( #{self.class} #{self.to_s} #{@children.inspect} )" end # don't fetch lexer comment by default def use_docs self.class.use_docs end # allow our subclass to specify they want documentation class << self attr_accessor :use_docs def associates_doc self.use_docs = true end end # Evaluate the current object. Just a stub method, since the subclass # should override this method. def evaluate(*options) end # Throw a parse error. def parsefail(message) self.fail(Puppet::ParseError, message) end # Wrap a statemp in a reusable way so we always throw a parse error. def parsewrap exceptwrap :type => Puppet::ParseError do yield end 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(*options) # We duplicate code here, rather than using exceptwrap, because this # is called so many times during parsing. begin return self.evaluate(*options) 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 settor # method for them. This is probably pretty inefficient and should # likely be changed at some point. def initialize(args) set_options(args) end # evaluate ourselves, and match def evaluate_match(value, scope) obj = self.safeevaluate(scope) obj = obj.downcase if obj.respond_to?(:downcase) value = value.downcase if value.respond_to?(:downcase) obj = Puppet::Parser::Scope.number?(obj) || obj value = Puppet::Parser::Scope.number?(value) || value # "" == undef for case/selector/if obj == value or (obj == "" and value == :undef) or (obj == :undef and value == "") end end # And include all of the AST subclasses. require 'puppet/parser/ast/arithmetic_operator' require 'puppet/parser/ast/astarray' require 'puppet/parser/ast/asthash' require 'puppet/parser/ast/boolean_operator' require 'puppet/parser/ast/branch' require 'puppet/parser/ast/caseopt' require 'puppet/parser/ast/casestatement' require 'puppet/parser/ast/collection' require 'puppet/parser/ast/collexpr' require 'puppet/parser/ast/comparison_operator' require 'puppet/parser/ast/definition' require 'puppet/parser/ast/else' require 'puppet/parser/ast/function' require 'puppet/parser/ast/hostclass' require 'puppet/parser/ast/ifstatement' require 'puppet/parser/ast/in_operator' require 'puppet/parser/ast/lambda' require 'puppet/parser/ast/leaf' require 'puppet/parser/ast/match_operator' require 'puppet/parser/ast/method_call' require 'puppet/parser/ast/minus' require 'puppet/parser/ast/node' require 'puppet/parser/ast/nop' require 'puppet/parser/ast/not' require 'puppet/parser/ast/relationship' require 'puppet/parser/ast/resource' require 'puppet/parser/ast/resource_defaults' require 'puppet/parser/ast/resource_instance' require 'puppet/parser/ast/resource_override' require 'puppet/parser/ast/resource_reference' require 'puppet/parser/ast/resourceparam' require 'puppet/parser/ast/selector' require 'puppet/parser/ast/vardef' require 'puppet/parser/code_merger' �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/�����������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017117� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/arithmetic_operator.rb�������������������������������������������0000664�0052762�0001160�00000006335�12650174557�023520� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/parser/ast/branch' class Puppet::Parser::AST class ArithmeticOperator < AST::Branch attr_accessor :operator, :lval, :rval # Iterate across all of our children. def each [@lval,@rval,@operator].each { |child| yield child } end # Produces an object which is the result of the applying the operator to the of lval and rval operands. # * Supports +, -, *, /, %, and <<, >> on numeric strings. # * Supports + on arrays (concatenate), and hashes (merge) # * Supports << on arrays (append) # def evaluate(scope) # evaluate the operands, should return a boolean value left = @lval.safeevaluate(scope) right = @rval.safeevaluate(scope) if left.is_a?(Array) || right.is_a?(Array) eval_array(left, right) elsif left.is_a?(Hash) || right.is_a?(Hash) eval_hash(left, right) else eval_numeric(left, right) end end # Concatenates (+) two arrays, or appends (<<) any object to a newly created array. # def eval_array(left, right) assert_concatenation_supported() raise ArgumentError, "operator #{@operator} is not applicable when one of the operands is an Array." unless %w{+ <<}.include?(@operator) raise ArgumentError, "left operand of #{@operator} must be an Array" unless left.is_a?(Array) if @operator == '+' raise ArgumentError, "right operand of #{@operator} must be an Array when left is an Array." unless right.is_a?(Array) return left + right end # only append case remains, left asserted to be an array, and right may be any object # wrapping right in an array and adding it ensures a new copy (operator << mutates). # left + [right] end # Merges two hashes. # def eval_hash(left, right) assert_concatenation_supported() raise ArgumentError, "operator #{@operator} is not applicable when one of the operands is an Hash." unless @operator == '+' raise ArgumentError, "left operand of #{@operator} must be an Hash" unless left.is_a?(Hash) raise ArgumentError, "right operand of #{@operator} must be an Hash" unless right.is_a?(Hash) # merge produces a merged copy left.merge(right) end def eval_numeric(left, right) left = Puppet::Parser::Scope.number?(left) right = Puppet::Parser::Scope.number?(right) raise ArgumentError, "left operand of #{@operator} is not a number" unless left != nil raise ArgumentError, "right operand of #{@operator} is not a number" unless right != nil # compute result left.send(@operator, right) end def assert_concatenation_supported return if Puppet.future_parser? raise ParseError.new("Unsupported Operation: Array concatenation available with '--parser future' setting only.") end def initialize(hash) super raise ArgumentError, "Invalid arithmetic operator #{@operator}" unless %w{+ - * / % << >>}.include?(@operator) end end # Used by future parser instead of ArithmeticOperator to enable concatenation class ArithmeticOperator2 < ArithmeticOperator # Overrides the arithmetic operator to allow concatenation def assert_concatenation_supported end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/astarray.rb������������������������������������������������������0000664�0052762�0001160�00000002574�12650174557�021303� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/branch' class Puppet::Parser::AST # The basic container class. This object behaves almost identically # to a normal array except at initialization time. Note that its name # is 'AST::ASTArray', rather than plain 'AST::Array'; I had too many # bugs when it was just 'AST::Array', because things like # 'object.is_a?(Array)' never behaved as I expected. class ASTArray < Branch include Enumerable # Return a child by index. Used (at least) by tests. def [](index) @children[index] end # Evaluate our children. def evaluate(scope) result = [] @children.each do |child| # Skip things that respond to :instantiate (classes, nodes, # and definitions), because they have already been # instantiated. if !child.respond_to?(:instantiate) item = child.safeevaluate(scope) if !item.nil? # nil values are implicitly removed. result.push(item) end end end result end def push(*ary) ary.each { |child| #Puppet.debug "adding %s(%s) of type %s to %s" % # [child, child.object_id, child.class.to_s.sub(/.+::/,''), # self.object_id] @children.push(child) } self end def to_s "[" + @children.collect { |c| c.to_s }.join(', ') + "]" end end end ������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/asthash.rb�������������������������������������������������������0000664�0052762�0001160�00000001331�12650174557�021076� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/leaf' class Puppet::Parser::AST class ASTHash < Leaf include Enumerable # Evaluate our children. def evaluate(scope) items = {} @value.each_pair do |k,v| key = k.respond_to?(:safeevaluate) ? k.safeevaluate(scope) : k items.merge!({ key => v.safeevaluate(scope) }) end items end def merge(hash) case hash when ASTHash @value = @value.merge(hash.value) when Hash @value = @value.merge(hash) end end def to_s "{" + @value.collect { |v| v.collect { |a| a.to_s }.join(' => ') }.join(', ') + "}" end def initialize(args) super(args) @value ||= {} end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/block_expression.rb����������������������������������������������0000664�0052762�0001160�00000001602�12650174557�023015� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/branch' class Puppet::Parser::AST class BlockExpression < Branch include Enumerable # Evaluate contained expressions, produce result of the last def evaluate(scope) result = nil @children.each do |child| result = child.safeevaluate(scope) end result end # Return a child by index. def [](index) @children[index] end def push(*ary) ary.each { |child| #Puppet.debug "adding %s(%s) of type %s to %s" % # [child, child.object_id, child.class.to_s.sub(/.+::/,''), # self.object_id] @children.push(child) } self 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 end ������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/boolean_operator.rb����������������������������������������������0000664�0052762�0001160�00000002233�12650174557�022777� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/parser/ast/branch' class Puppet::Parser::AST class BooleanOperator < AST::Branch attr_accessor :operator, :lval, :rval # Iterate across all of our children. def each [@lval,@rval,@operator].each { |child| yield child } end # Returns a boolean which is the result of the boolean operation # of lval and rval operands def evaluate(scope) # evaluate the first operand, should return a boolean value lval = @lval.safeevaluate(scope) # return result # lazy evaluate right operand case @operator when "and" if Puppet::Parser::Scope.true?(lval) rval = @rval.safeevaluate(scope) Puppet::Parser::Scope.true?(rval) else # false and false == false false end when "or" if Puppet::Parser::Scope.true?(lval) true else rval = @rval.safeevaluate(scope) Puppet::Parser::Scope.true?(rval) end end end def initialize(hash) super raise ArgumentError, "Invalid boolean operator #{@operator}" unless %w{and or}.include?(@operator) end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/branch.rb��������������������������������������������������������0000664�0052762�0001160�00000001074�12650174557�020704� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Parser::AST # 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 descendent of the AST::Leaf class. class Branch < AST include Enumerable attr_accessor :pin, :children def each @children.each { |child| yield child } end def initialize(arghash) super(arghash) @children ||= [] end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/caseopt.rb�������������������������������������������������������0000664�0052762�0001160�00000002231�12650174557�021101� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/branch' class Puppet::Parser::AST # Each individual option in a case statement. class CaseOpt < AST::Branch attr_accessor :value, :statements # CaseOpt is a bit special -- we just want the value first, # so that CaseStatement can compare, and then it will selectively # decide whether to fully evaluate this option def each [@value,@statements].each { |child| yield child } end # Are we the default option? def default? # Cache the @default value. return @default if defined?(@default) @value.each { |subval| if subval.is_a?(AST::Default) @default = true break end } @default ||= false @default end # You can specify a list of values; return each in turn. def eachvalue(scope) @value.each { |subval| yield subval.safeevaluate(scope) } end def eachopt @value.each { |subval| yield subval } end # Evaluate the actual statements; this only gets called if # our option matched. def evaluate(scope) @statements.safeevaluate(scope) end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/casestatement.rb�������������������������������������������������0000664�0052762�0001160�00000002066�12650174557�022311� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/branch' class Puppet::Parser::AST # The basic logical structure in Puppet. Supports a list of # tests and statement arrays. class CaseStatement < AST::Branch attr_accessor :test, :options, :default associates_doc # Short-curcuit evaluation. Return the value of the statements for # the first option that matches. def evaluate(scope) level = scope.ephemeral_level value = @test.safeevaluate(scope) # Iterate across the options looking for a match. default = nil @options.each do |option| option.eachopt do |opt| return option.safeevaluate(scope) if opt.evaluate_match(value, scope) end default = option if option.default? end # Unless we found something, look for the default. return default.safeevaluate(scope) if default Puppet.debug "No true answers and no default" return nil ensure scope.unset_ephemeral_var(level) end def each [@test,@options].each { |child| yield child } end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/collection.rb����������������������������������������������������0000664�0052762�0001160�00000002741�12650174557�021604� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/parser/ast/branch' require 'puppet/parser/collector' # An object that collects stored objects from the central cache and returns # them to the current host, yo. class Puppet::Parser::AST class Collection < AST::Branch attr_accessor :type, :query, :form attr_reader :override associates_doc # We return an object that does a late-binding evaluation. def evaluate(scope) match, code = query && query.safeevaluate(scope) if @type == 'class' fail "Classes cannot be collected" end resource_type = scope.find_resource_type(@type) fail "Resource type #{@type} doesn't exist" unless resource_type newcoll = Puppet::Parser::Collector.new(scope, resource_type.name, match, code, self.form) scope.compiler.add_collection(newcoll) # overrides if any # Evaluate all of the specified params. if @override params = @override.collect { |param| param.safeevaluate(scope) } newcoll.add_override( :parameters => params, :file => @file, :line => @line, :source => scope.source, :scope => scope ) end newcoll end # Handle our parameter ourselves def override=(override) @override = if override.is_a?(AST::ASTArray) override else AST::ASTArray.new(:line => override.line,:file => override.file,:children => [override]) end end end end �������������������������������puppet-3.8.5/lib/puppet/parser/ast/collexpr.rb������������������������������������������������������0000664�0052762�0001160�00000006351�12650174557�021302� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/parser/ast/branch' require 'puppet/parser/collector' # An object that collects stored objects from the central cache and returns # them to the current host, yo. class Puppet::Parser::AST class CollExpr < AST::Branch attr_accessor :test1, :test2, :oper, :form, :type, :parens def evaluate(scope) if Puppet.future_parser? evaluate4x(scope) else evaluate3x(scope) end end # We return an object that does a late-binding evaluation. def evaluate3x(scope) # Make sure our contained expressions have all the info they need. [@test1, @test2].each do |t| if t.is_a?(self.class) t.form ||= self.form t.type ||= self.type end end # The code is only used for virtual lookups match1, code1 = @test1.safeevaluate scope match2, code2 = @test2.safeevaluate scope # First build up the virtual code. # If we're a conjunction operator, then we're calling code. I did # some speed comparisons, and it's at least twice as fast doing these # case statements as doing an eval here. code = proc do |resource| case @oper when "and"; code1.call(resource) and code2.call(resource) when "or"; code1.call(resource) or code2.call(resource) when "==" if match1 == "tag" resource.tagged?(match2) else if resource[match1].is_a?(Array) resource[match1].include?(match2) else resource[match1] == match2 end end when "!="; resource[match1] != match2 end end match = [match1, @oper, match2] return match, code end # Late binding evaluation of a collect expression (as done in 3x), but with proper Puppet Language # semantics for equals and include # def evaluate4x(scope) # Make sure our contained expressions have all the info they need. [@test1, @test2].each do |t| if t.is_a?(self.class) t.form ||= self.form t.type ||= self.type end end # The code is only used for virtual lookups match1, code1 = @test1.safeevaluate scope match2, code2 = @test2.safeevaluate scope # First build up the virtual code. # If we're a conjunction operator, then we're calling code. I did # some speed comparisons, and it's at least twice as fast doing these # case statements as doing an eval here. code = proc do |resource| case @oper when "and"; code1.call(resource) and code2.call(resource) when "or"; code1.call(resource) or code2.call(resource) when "==" if match1 == "tag" resource.tagged?(match2) else if resource[match1].is_a?(Array) @@compare_operator.include?(resource[match1], match2, scope) else @@compare_operator.equals(resource[match1], match2) end end when "!="; ! @@compare_operator.equals(resource[match1], match2) end end match = [match1, @oper, match2] return match, code end def initialize(hash = {}) super if Puppet.future_parser? @@compare_operator ||= Puppet::Pops::Evaluator::CompareOperator.new end raise ArgumentError, "Invalid operator #{@oper}" unless %w{== != and or}.include?(@oper) end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/comparison_operator.rb�������������������������������������������0000664�0052762�0001160�00000002012�12650174557�023525� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/parser/ast/branch' class Puppet::Parser::AST class ComparisonOperator < AST::Branch attr_accessor :operator, :lval, :rval # Iterate across all of our children. def each [@lval,@rval,@operator].each { |child| yield child } end # Returns a boolean which is the result of the boolean operation # of lval and rval operands def evaluate(scope) # evaluate the operands, should return a boolean value lval = @lval.safeevaluate(scope) case @operator when "==","!=" @rval.evaluate_match(lval, scope) ? @operator == '==' : @operator == '!=' else rval = @rval.safeevaluate(scope) rval = Puppet::Parser::Scope.number?(rval) || rval lval = Puppet::Parser::Scope.number?(lval) || lval lval.send(@operator,rval) end end def initialize(hash) super raise ArgumentError, "Invalid comparison operator #{@operator}" unless %w{== != < > <= >=}.include?(@operator) end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/definition.rb����������������������������������������������������0000664�0052762�0001160�00000000764�12650174557�021604� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/top_level_construct' class Puppet::Parser::AST::Definition < Puppet::Parser::AST::TopLevelConstruct attr_accessor :context def initialize(name, context = {}, &ruby_code) @name = name @context = context @ruby_code = ruby_code end def instantiate(modname) new_definition = Puppet::Resource::Type.new(:definition, @name, @context.merge(:module_name => modname)) new_definition.ruby_code = @ruby_code if @ruby_code [new_definition] end end ������������puppet-3.8.5/lib/puppet/parser/ast/else.rb����������������������������������������������������������0000664�0052762�0001160�00000000676�12650174557�020406� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/branch' class Puppet::Parser::AST # A separate ElseIf statement; can function as an 'else' if there's no # test. class Else < AST::Branch associates_doc attr_accessor :statements def each yield @statements end # Evaluate the actual statements; this only gets called if # our test was true matched. def evaluate(scope) @statements.safeevaluate(scope) end end end ������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/function.rb������������������������������������������������������0000664�0052762�0001160�00000003676�12650174557�021306� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/branch' class Puppet::Parser::AST # An AST object to call a function. class Function < AST::Branch associates_doc attr_accessor :name, :arguments, :pblock def evaluate(scope) # Make sure it's a defined function raise Puppet::ParseError, "Unknown function #{@name}" unless Puppet::Parser::Functions.function(@name) # Now check that it's been used correctly case @ftype when :rvalue raise Puppet::ParseError, "Function '#{@name}' does not return a value" unless Puppet::Parser::Functions.rvalue?(@name) when :statement # It is harmless to produce an ignored rvalue, the alternative is to mark functions # as appropriate for both rvalue and statements # Keeping the old behavior when a pblock is not present. This since it is not known # if the lambda contains a statement or not (at least not without a costly search). # The purpose of the check is to protect a user for producing a meaningless rvalue where the # operation has no side effects. # if !pblock && Puppet::Parser::Functions.rvalue?(@name) raise Puppet::ParseError, "Function '#{@name}' must be the value of a statement" end else raise Puppet::DevError, "Invalid function type #{@ftype.inspect}" end # We don't need to evaluate the name, because it's plaintext args = @arguments.safeevaluate(scope).map { |x| x == :undef ? '' : x } # append a puppet lambda (unevaluated) if it is defined args << pblock if pblock scope.send("function_#{@name}", args) end def initialize(hash) @ftype = hash[:ftype] || :rvalue hash.delete(:ftype) if hash.include? :ftype super(hash) # Lastly, check the parity end def to_s args = arguments.is_a?(ASTArray) ? arguments.to_s.gsub(/\[(.*)\]/,'\1') : arguments "#{name}(#{args})" end end end ������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/hostclass.rb�����������������������������������������������������0000664�0052762�0001160�00000001362�12650174557�021452� 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 = {}, &ruby_code) @context = context @name = name @ruby_code = ruby_code end def instantiate(modname) new_class = Puppet::Resource::Type.new(:hostclass, @name, @context.merge(:module_name => modname)) new_class.ruby_code = @ruby_code if @ruby_code 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-3.8.5/lib/puppet/parser/ast/ifstatement.rb���������������������������������������������������0000664�0052762�0001160�00000001562�12650174557�021774� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/branch' class Puppet::Parser::AST # A basic 'if/elsif/else' statement. class IfStatement < AST::Branch associates_doc attr_accessor :test, :else, :statements def each [@test,@else,@statements].each { |child| yield child } end # Short-curcuit evaluation. If we're true, evaluate our statements, # else if there's an 'else' setting, evaluate it. # the first option that matches. def evaluate(scope) level = scope.ephemeral_level value = @test.safeevaluate(scope) # let's emulate a new scope for each branches begin if Puppet::Parser::Scope.true?(value) return @statements.safeevaluate(scope) else return defined?(@else) ? @else.safeevaluate(scope) : nil end ensure scope.unset_ephemeral_var(level) end end end end ����������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/in_operator.rb���������������������������������������������������0000664�0052762�0001160�00000001361�12650174557�021767� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/parser/ast/branch' class Puppet::Parser::AST class InOperator < AST::Branch attr_accessor :lval, :rval # Returns a boolean which is the result of the 'in' operation # of lval and rval operands def evaluate(scope) # evaluate the operands, should return a boolean value lval = @lval.safeevaluate(scope) raise ArgumentError, "'#{lval}' from left operand of 'in' expression is not a string" unless lval.is_a?(::String) rval = @rval.safeevaluate(scope) unless rval.respond_to?(:include?) raise ArgumentError, "'#{rval}' from right operand of 'in' expression is not of a supported type (string, array or hash)" end rval.include?(lval) end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/lambda.rb��������������������������������������������������������0000664�0052762�0001160�00000011721�12650174557�020667� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/block_expression' class Puppet::Parser::AST # A block of statements/expressions with additional parameters # Requires scope to contain the values for the defined parameters when evaluated # If evaluated without a prepared scope, the lambda will behave like its super class. # class Lambda < AST::BlockExpression # The lambda parameters. # These are encoded as an array where each entry is an array of one or two object. The first # is the parameter name, and the optional second object is the value expression (that will # be evaluated when bound to a scope). # The value expression is the default value for the parameter. All default values must be # at the end of the parameter list. # # @return [Array<Array<String,String>>] list of parameter names with optional value expression attr_accessor :parameters # Evaluates each expression/statement and produce the last expression evaluation result # @return [Object] what the last expression evaluated to def evaluate(scope) if @children.is_a? Puppet::Parser::AST::ASTArray result = nil @children.each {|expr| result = expr.evaluate(scope) } result else @children.evaluate(scope) end end # Calls the lambda. # Assigns argument values in a nested local scope that should be used to evaluate the lambda # and then evaluates the lambda. # @param scope [Puppet::Scope] the calling scope # @return [Object] the result of evaluating the expression(s) in the lambda # def call(scope, *args) raise Puppet::ParseError, "Too many arguments: #{args.size} for #{parameters.size}" unless args.size <= parameters.size # associate values with parameters merged = parameters.zip(args) # calculate missing arguments missing = parameters.slice(args.size, parameters.size - args.size).select {|e| e.size == 1} unless missing.empty? optional = parameters.count { |p| p.size == 2 } raise Puppet::ParseError, "Too few arguments; #{args.size} for #{optional > 0 ? ' min ' : ''}#{parameters.size - optional}" end evaluated = merged.collect do |m| # m can be one of # m = [["name"], "given"] # | [["name", default_expr], "given"] # # "given" is always an optional entry. If a parameter was provided then # the entry will be in the array, otherwise the m array will be a # single element. given_argument = m[1] argument_name = m[0][0] default_expression = m[0][1] value = if m.size == 1 default_expression.safeevaluate(scope) else given_argument end [argument_name, value] end # Store the evaluated name => value associations in a new inner/local/ephemeral scope # (This is made complicated due to the fact that the implementation of scope is overloaded with # functionality and an inner ephemeral scope must be used (as opposed to just pushing a local scope # on a scope "stack"). # Ensure variable exists with nil value if error occurs. # Some ruby implementations does not like creating variable on return result = nil begin elevel = scope.ephemeral_level scope.ephemeral_from(Hash[evaluated], file, line) result = safeevaluate(scope) ensure scope.unset_ephemeral_var(elevel) end result end # Validates the lambda. # Validation checks if parameters with default values are at the end of the list. (It is illegal # to have a parameter with default value followed by one without). # # @raise [Puppet::ParseError] if a parameter with a default comes before a parameter without default value # def validate params = parameters || [] defaults = params.drop_while {|p| p.size < 2 } trailing = defaults.drop_while {|p| p.size == 2 } raise Puppet::ParseError, "Lambda parameters with default values must be placed last" unless trailing.empty? end # Returns the number of parameters (required and optional) # @return [Integer] the total number of accepted parameters def parameter_count @parameters.size end # Returns the number of optional parameters. # @return [Integer] the number of optional accepted parameters def optional_parameter_count @parameters.count {|p| p.size == 2 } end def initialize(options) super(options) # ensure there is an empty parameters structure if not given by creator @parameters = [] unless options[:parameters] validate end def to_s result = ["{|"] result += @parameters.collect {|p| "#{p[0]}" + (p.size == 2 && p[1]) ? p[1].to_s() : '' }.join(', ') result << "| ... }" result.join('') end # marker method checked with respond_to :puppet_lambda def puppet_lambda() true end def parameter_names @parameters.collect {|p| p[0] } end end end �����������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/leaf.rb����������������������������������������������������������0000664�0052762�0001160�00000013664�12650174557�020366� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Parser::AST # 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 Leaf < 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 # The boolean class. True or false. Converts the string it receives # to a Ruby boolean. class Boolean < AST::Leaf def initialize(hash) super unless @value == true or @value == false raise Puppet::DevError, "'#{@value}' is not a boolean" end @value end end # The base string class. class String < AST::Leaf def evaluate(scope) @value.dup end def to_s @value.inspect end end # An uninterpreted string. class FlatString < AST::Leaf def evaluate(scope) @value end def to_s @value.inspect end end class Concat < AST::Leaf def evaluate(scope) @value.collect { |x| x.evaluate(scope) }.collect{ |x| x == :undef ? '' : x }.join end def to_s "#{@value.map { |s| s.to_s.gsub(/^"(.*)"$/, '\1') }.join}" end end # The 'default' option on case statements and selectors. class Default < AST::Leaf; end # Capitalized words; used mostly for type-defaults, but also # get returned by the lexer any other time an unquoted capitalized # word is found. class Type < AST::Leaf; end # Lower-case words. class Name < AST::Leaf; end # double-colon separated class names class ClassName < AST::Leaf; end # undef values; equiv to nil class Undef < AST::Leaf; end # Host names, either fully qualified or just the short name, or even a regex class HostName < 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 @value =~ /[^-\w.]/ and raise Puppet::DevError, "'#{@value}' is not a valid hostname" 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 = value.value if value.is_a?(HostName) @value.eql?(value) end def hash @value.hash end end # A simple variable. This object is only used during interpolation; # the VarDef class is used for assignment. class Variable < Name # Looks up the value of the object in the scope tree (does # not include syntactical constructs, like '$' and '{}'). def evaluate(scope) parsewrap do if scope.include?(@value) scope[@value, {:file => file, :line => line}] else :undef end end end def to_s "\$#{value}" end end class HashOrArrayAccess < AST::Leaf attr_accessor :variable, :key def evaluate_container(scope) container = variable.respond_to?(:evaluate) ? variable.safeevaluate(scope) : variable if container.is_a?(Hash) || container.is_a?(Array) container elsif container.is_a?(::String) scope[container, {:file => file, :line => line}] else raise Puppet::ParseError, "#{variable} is #{container.inspect}, not a hash or array" end end def evaluate_key(scope) key.respond_to?(:evaluate) ? key.safeevaluate(scope) : key end def array_index_or_key(object, key) if object.is_a?(Array) raise Puppet::ParseError, "#{key} is not an integer, but is used as an index of an array" unless key = Puppet::Parser::Scope.number?(key) end key end def evaluate(scope) object = evaluate_container(scope) accesskey = evaluate_key(scope) raise Puppet::ParseError, "#{variable} is not a hash or array when accessing it with #{accesskey}" unless object.is_a?(Hash) or object.is_a?(Array) result = object[array_index_or_key(object, accesskey)] result.nil? ? :undef : result end # Assign value to this hashkey or array index def assign(scope, value) object = evaluate_container(scope) accesskey = evaluate_key(scope) if object.is_a?(Hash) and object.include?(accesskey) raise Puppet::ParseError, "Assigning to the hash '#{variable}' with an existing key '#{accesskey}' is forbidden" end mutation_deprecation() # assign to hash or array object[array_index_or_key(object, accesskey)] = value end def to_s "\$#{variable.to_s}[#{key.to_s}]" end def mutation_deprecation deprecation_location_text = if file && line " at #{file}:#{line}" elsif file " in file #{file}" elsif line " at #{line}" end Puppet.warning(["The use of mutating operations on Array/Hash is deprecated#{deprecation_location_text}.", " See http://links.puppetlabs.com/puppet-mutation-deprecation"].join('')) end end class Regex < AST::Leaf def initialize(hash) super @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 evaluate_match(value, scope, options = {}) value = value == :undef ? '' : value.to_s if matched = @value.match(value) scope.ephemeral_from(matched, options[:file], options[:line]) end matched end def match(value) @value.match(value) end def to_s "/#{@value.source}/" end end end ����������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/match_operator.rb������������������������������������������������0000664�0052762�0001160�00000001264�12650174557�022457� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/parser/ast/branch' class Puppet::Parser::AST class MatchOperator < AST::Branch attr_accessor :lval, :rval, :operator # Iterate across all of our children. def each [@lval,@rval].each { |child| yield child } end # Returns a boolean which is the result of the boolean operation # of lval and rval operands def evaluate(scope) lval = @lval.safeevaluate(scope) return(rval.evaluate_match(lval, scope) ? @operator == "=~" : @operator == "!~") end def initialize(hash) super raise ArgumentError, "Invalid regexp operator #{@operator}" unless %w{!~ =~}.include?(@operator) end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/method_call.rb���������������������������������������������������0000664�0052762�0001160�00000005127�12650174557�021725� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/branch' require 'puppet/parser/methods' class Puppet::Parser::AST # An AST object to call a method class MethodCall < AST::Branch associates_doc # An AST that evaluates to the object the method is applied to # @return [Puppet::Parser::AST] attr_accessor :receiver # The name of the method # @return [String] attr_accessor :name # The arguments to evaluate as arguments to the method. # @return [Array<Puppet::Parser::AST>] attr_accessor :arguments # An optional lambda/block that will be yielded to by the called method (if it supports this) # @return [Puppet::Parser::AST::Lambda] attr_accessor :lambda # Evaluates the method call and returns what the called method/function returns. # The evaluation evaluates all arguments in the calling scope and then delegates # to a "method" instance produced by Puppet::Parser::Methods for this method call. # @see Puppet::Parser::Methods # @return [Object] what the called method/function returns def evaluate(scope) # Make sure it's a defined method for the receiver r = @receiver.evaluate(scope) raise Puppet::ParseError, "No object to apply method #{@name} to" unless r m = Puppet::Parser::Methods.find_method(scope, r, @name) raise Puppet::ParseError, "Unknown method #{@name} for #{r}" unless m # Now check if rvalue is required (in expressions) case @ftype when :rvalue raise Puppet::ParseError, "Method '#{@name}' does not return a value" unless m.is_rvalue? when :statement # When used as a statement, ignore if it produces a rvalue (it is simply not used) else raise Puppet::DevError, "Invalid method type #{@ftype.inspect}" end # Evaluate arguments args = @arguments ? @arguments.safeevaluate(scope).map { |x| x == :undef ? '' : x } : [] # There is no need to evaluate the name, since it is a literal ruby string # call the method (it is already bound to the receiver and name) m.invoke(scope, args, @lambda) end def initialize(hash) @ftype = hash[:ftype] || :rvalue hash.delete(:ftype) if hash.include? :ftype super(hash) # Lastly, check the parity end # Sets this method call in statement mode where a produced rvalue is ignored. # @return [void] def ignore_rvalue @ftype = :statement end def to_s args = arguments.is_a?(ASTArray) ? arguments.to_s.gsub(/\[(.*)\]/,'\1') : arguments "#{@receiver.to_s}.#{name} (#{args})" + (@lambda ? " #{@lambda.to_s}" : '') end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/minus.rb���������������������������������������������������������0000664�0052762�0001160�00000000755�12650174557�020607� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/parser/ast/branch' # An object that returns a boolean which is the boolean not # of the given value. class Puppet::Parser::AST class Minus < AST::Branch attr_accessor :value def each yield @value end def evaluate(scope) val = @value.safeevaluate(scope) val = Puppet::Parser::Scope.number?(val) if val == nil raise ArgumentError, "minus operand #{val} is not a number" end -val end end end �������������������puppet-3.8.5/lib/puppet/parser/ast/node.rb����������������������������������������������������������0000664�0052762�0001160�00000001634�12650174557�020376� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/top_level_construct' class Puppet::Parser::AST::Node < Puppet::Parser::AST::TopLevelConstruct attr_accessor :names, :context def initialize(names, context = {}, &ruby_code) raise ArgumentError, "names should be an array" unless names.is_a? Array if context[:parent] msg = "Deprecation notice: Node inheritance is not supported in Puppet >= 4.0.0. See http://links.puppetlabs.com/puppet-node-inheritance-deprecation" Puppet.puppet_deprecation_warning(msg, :key => "node-inheritance-#{names.join}", :file => context[:file], :line => context[:line]) end @names = names @context = context @ruby_code = ruby_code end def instantiate(modname) @names.collect do |name| new_node = Puppet::Resource::Type.new(:node, name, @context.merge(:module_name => modname)) new_node.ruby_code = @ruby_code if @ruby_code new_node end end end ����������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/nop.rb�����������������������������������������������������������0000664�0052762�0001160�00000000364�12650174557�020244� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/branch' class Puppet::Parser::AST # This class is a no-op, it doesn't produce anything # when evaluated, hence its name :-) class Nop < AST::Leaf def evaluate(scope) # nothing to do end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/not.rb�����������������������������������������������������������0000664�0052762�0001160�00000000570�12650174557�020247� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/parser/ast/branch' # An object that returns a boolean which is the boolean not # of the given value. class Puppet::Parser::AST class Not < AST::Branch attr_accessor :value def each yield @value end def evaluate(scope) val = @value.safeevaluate(scope) ! Puppet::Parser::Scope.true?(val) end end end ����������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/pops_bridge.rb���������������������������������������������������0000664�0052762�0001160�00000022620�12650174557�021744� 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 # 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.collect 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::NodeDefinition instantiate_NodeDefinition(d, modname) else raise Puppet::ParseError, "Internal Error: Unknown type of definition - got '#{d.class}'" end end.flatten().compact() # flatten since node definition may have returned an array # Compact since functions 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 private 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) 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] = Expression.new(:value => o.body) end @ast_transformer.merge_location(args, o) end def instantiate_HostClassDefinition(o, modname) args = args_from_definition(o, modname) args[:parent] = absolute_reference(o.parent_class) Puppet::Resource::Type.new(:hostclass, o.name, @context.merge(args)) end def instantiate_ResourceTypeDefinition(o, modname) Puppet::Resource::Type.new(:definition, o.name, @context.merge(args_from_definition(o, modname))) 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 host_matches = @ast_transformer.hostname(o.host_matches) @ast_transformer.merge_location(args, o) host_matches.collect do |name| Puppet::Resource::Type.new(:node, name, @context.merge(args)) end end # Propagates a found Function to the appropriate loader. # This is for 4x future-evaluator/loader # def instantiate_FunctionDefinition(function_definition, modname) loaders = (Puppet.lookup(:loaders) { nil }) unless loaders raise Puppet::ParseError, "Internal Error: Puppet Context ':loaders' missing - cannot define any functions" end loader = if modname.nil? || modname == "" # TODO : Later when functions can be private, a decision is needed regarding what that means. # A private environment loader could be used for logic outside of modules, then only that logic # would see the function. # # Use the private loader, this function may see the environment's dependencies (currently, all modules) loaders.private_environment_loader() else # TODO : Later check if function is private, and then add it to # private_loader_for_module # loaders.public_loader_for_module(modname) end unless loader raise Puppet::ParseError, "Internal Error: did not find public loader for module: '#{modname}'" end # Instantiate Function, and store it in the environment loader typed_name, f = Puppet::Pops::Loader::PuppetFunctionInstantiator.create_from_model(function_definition, loader) loader.set_entry(typed_name, f, Puppet::Pops::Adapters::SourcePosAdapter.adapt(function_definition).to_uri) nil # do not want the function to inadvertently leak into 3x 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-3.8.5/lib/puppet/parser/ast/relationship.rb��������������������������������������������������0000664�0052762�0001160�00000002275�12650174557�022154� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast' require 'puppet/parser/ast/branch' require 'puppet/parser/relationship' class Puppet::Parser::AST::Relationship < Puppet::Parser::AST::Branch RELATIONSHIP_TYPES = %w{-> <- ~> <~} attr_accessor :left, :right, :arrow, :type # Evaluate our object, but just return a simple array of the type # and name. def evaluate(scope) real_left = left.safeevaluate(scope) real_right = right.safeevaluate(scope) source, target = sides2edge(real_left, real_right) scope.compiler.add_relationship Puppet::Parser::Relationship.new(source, target, type) real_right end def initialize(left, right, arrow, args = {}) super(args) unless RELATIONSHIP_TYPES.include?(arrow) raise ArgumentError, "Invalid relationship type #{arrow.inspect}; valid types are #{RELATIONSHIP_TYPES.collect { |r| r.to_s }.join(", ")}" end @left, @right, @arrow = left, right, arrow end def type subscription? ? :subscription : :relationship end def sides2edge(left, right) out_edge? ? [left, right] : [right, left] end private def out_edge? ["->", "~>"].include?(arrow) end def subscription? ["~>", "<~"].include?(arrow) end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/resource.rb������������������������������������������������������0000664�0052762�0001160�00000004654�12650174557�021305� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/resource_reference' # Any normal puppet resource declaration. Can point to a definition or a # builtin type. class Puppet::Parser::AST class Resource < AST::Branch associates_doc attr_accessor :type, :instances, :exported, :virtual # Does not actually return an object; instead sets an object # in the current scope. 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.collect { |instance| # Evaluate all of the specified params. paramobjects = instance.parameters.collect { |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.collect { |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, true) if fully_qualified_type == 'class' resource end } }.flatten.reject { |resource| resource.nil? } end end end ������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/resource_defaults.rb���������������������������������������������0000664�0052762�0001160�00000001225�12650174557�023163� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/branch' class Puppet::Parser::AST # A statement syntactically similar to an ResourceDef, but uses a # capitalized object type and cannot have a name. class ResourceDefaults < AST::Branch attr_accessor :type, :parameters associates_doc # As opposed to ResourceDef, this stores each default for the given # object type. def evaluate(scope) # Use a resource reference to canonize the type ref = Puppet::Resource.new(@type, "whatever") type = ref.type params = @parameters.safeevaluate(scope) parsewrap do scope.define_settings(type, params) end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/resource_instance.rb���������������������������������������������0000664�0052762�0001160�00000000374�12650174557�023164� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/branch' class Puppet::Parser::AST class ResourceInstance < Branch # A simple container for a parameter for an object. Consists of a # title and a set of parameters. attr_accessor :title, :parameters end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/resource_override.rb���������������������������������������������0000664�0052762�0001160�00000003235�12650174557�023176� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/resource' class Puppet::Parser::AST # Set a parameter on a resource specification created somewhere else in the # configuration. The object is responsible for verifying that this is allowed. class ResourceOverride < AST::Branch associates_doc attr_accessor :object, :parameters # Iterate across all of our children. def each [@object,@parameters].flatten.each { |param| #Puppet.debug("yielding param #{param}") yield param } end # Does not actually return an object; instead sets an object # in the current scope. def evaluate(scope) # Get our object reference. resource = @object.safeevaluate(scope) # Evaluate all of the specified params. params = @parameters.collect { |param| param.safeevaluate(scope) } # Now we just create a normal resource, but we call a very different # method on the scope. resource = [resource] unless resource.is_a?(Array) resource = resource.collect do |r| res = Puppet::Parser::Resource.new( r.type, r.title, :parameters => params, :file => file, :line => line, :source => scope.source, :scope => scope ) # Now we tell the scope that it's an override, and it behaves as # necessary. scope.compiler.add_override(res) res end # decapsulate array in case of only one item return(resource.length == 1 ? resource.pop : resource) end # Create our ResourceDef. Handles type checking for us. def initialize(hash) @checked = false super end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/resource_reference.rb��������������������������������������������0000664�0052762�0001160�00000001327�12650174557�023315� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast' require 'puppet/parser/ast/branch' class Puppet::Parser::AST::ResourceReference < Puppet::Parser::AST::Branch attr_accessor :title, :type # Evaluate our object, but just return a simple array of the type # and name. def evaluate(scope) titles = Array(title.safeevaluate(scope)).flatten a_type, titles = scope.resolve_type_and_titles(type, titles) resources = titles.collect{ |a_title| Puppet::Resource.new(a_type, a_title) } return(resources.length == 1 ? resources.pop : resources) end def to_s if title.is_a?(Puppet::Parser::AST::ASTArray) "#{type.to_s.capitalize}#{title}" else "#{type.to_s.capitalize}[#{title}]" end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/resourceparam.rb�������������������������������������������������0000664�0052762�0001160�00000001275�12650174557�022322� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/branch' class Puppet::Parser::AST # The AST object for the parameters inside ResourceDefs and Selectors. class ResourceParam < AST::Branch attr_accessor :value, :param, :add 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 end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/selector.rb������������������������������������������������������0000664�0052762�0001160�00000002656�12650174557�021276� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/branch' class Puppet::Parser::AST # The inline conditional operator. Unlike CaseStatement, which executes # code, we just return a value. class Selector < AST::Branch attr_accessor :param, :values def each [@param,@values].each { |child| yield child } end # Find the value that corresponds with the test. def evaluate(scope) level = scope.ephemeral_level # Get our parameter. paramvalue = @param.safeevaluate(scope) default = nil @values = [@values] unless @values.instance_of? AST::ASTArray or @values.instance_of? Array # Then look for a match in the options. @values.each do |obj| # short circuit asap if we have a match return obj.value.safeevaluate(scope) if obj.param.evaluate_match(paramvalue, scope) # Store the default, in case it's necessary. default = obj if obj.param.is_a?(Default) end # Unless we found something, look for the default. return default.value.safeevaluate(scope) if default self.fail Puppet::ParseError, "No matching value for selector param '#{paramvalue}'" ensure scope.unset_ephemeral_var(level) end def to_s if @values.instance_of? AST::ASTArray or @values.instance_of? Array v = @values else v = [@values] end param.to_s + " ? { " + v.collect { |v| v.to_s }.join(', ') + " }" end end end ����������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/ast/top_level_construct.rb�������������������������������������������0000664�0052762�0001160�00000000254�12650174557�023543� 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-3.8.5/lib/puppet/parser/ast/vardef.rb��������������������������������������������������������0000664�0052762�0001160�00000001613�12650174557�020715� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/branch' class Puppet::Parser::AST # Define a variable. Stores the value in the current scope. class VarDef < AST::Branch associates_doc attr_accessor :name, :value, :append # Look up our name and value, and store them appropriately. The # lexer strips off the syntax stuff like '$'. def evaluate(scope) value = @value.safeevaluate(scope) if name.is_a?(HashOrArrayAccess) name.assign(scope, value) else name = @name.safeevaluate(scope) parsewrap do scope.setvar(name,value, :file => file, :line => line, :append => @append) end end if @append # Produce resulting value from append operation scope[name] else # Produce assigned value value end end def each [@name,@value].each { |child| yield child } end end end ���������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/code_merger.rb�������������������������������������������������������0000664�0052762�0001160�00000000645�12650174557�021136� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� class Puppet::Parser::CodeMerger # Concatenates the logic in the array of parse results into one parse result # @return Puppet::Parser::AST::BlockExpression # def concatenate(parse_results) children = parse_results.select {|x| !x.nil? && x.code}.reduce([]) do |memo, parsed_class| memo + parsed_class.code.children end Puppet::Parser::AST::BlockExpression.new(:children => children) end end �������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/collector.rb���������������������������������������������������������0000664�0052762�0001160�00000013211�12650174557�020642� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# An object that collects stored objects from the central cache and returns # them to the current host, yo. class Puppet::Parser::Collector attr_accessor :type, :scope, :vquery, :equery, :form attr_accessor :resources, :overrides, :collected # Call the collection method, mark all of the returned objects as # non-virtual, optionally applying parameter overrides. The collector can # also delete himself from the compiler if there is no more resources to # collect (valid only for resource fixed-set collector which get their # resources from +collect_resources+ and not from the catalog) def evaluate # Shortcut if we're not using storeconfigs and they're trying to collect # exported resources. if form == :exported and Puppet[:storeconfigs] != true Puppet.warning "Not collecting exported resources without storeconfigs" return false end if self.resources unless objects = collect_resources and ! objects.empty? return false end else method = "collect_#{@form.to_s}" objects = send(method).each do |obj| obj.virtual = false end return false if objects.empty? end # we have an override for the collected resources if @overrides and !objects.empty? # force the resource to be always child of any other resource overrides[:source].meta_def(:child_of?) do |klass| true end # tell the compiler we have some override for him unless we already # overrided those resources objects.each do |res| unless @collected.include?(res.ref) newres = Puppet::Parser::Resource. new(res.type, res.title, :parameters => overrides[:parameters], :file => overrides[:file], :line => overrides[:line], :source => overrides[:source], :scope => overrides[:scope]) scope.compiler.add_override(newres) end end end # filter out object that this collector has previously found. objects.reject! { |o| @collected.include?(o.ref) } return false if objects.empty? # keep an eye on the resources we have collected objects.inject(@collected) { |c,o| c[o.ref]=o; c } # return our newly collected resources objects end def initialize(scope, type, equery, vquery, form) @scope = scope @vquery = vquery @equery = equery # initialisation @collected = {} # Canonize the type @type = Puppet::Resource.new(type, "whatever").type unless [:exported, :virtual].include?(form) raise ArgumentError, "Invalid query form #{form}" end @form = form end # add a resource override to the soon to be exported/realized resources def add_override(hash) raise ArgumentError, "Exported resource try to override without parameters" unless hash[:parameters] # schedule an override for an upcoming collection @overrides = hash end private # Collect exported objects. def collect_exported resources = [] time = Puppet::Util.thinmark do # First get everything from the export table. Just reuse our # collect_virtual method but tell it to use 'exported? for the test. resources = collect_virtual(true).reject { |r| ! r.virtual? } # key is '#{type}/#{name}', and host and filter. 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.type, item.title) unless existing.collector_id == item.collector_id # unless this is the one we've already collected raise Puppet::ParseError, "A duplicate resource was found while collecting exported resources, with the type and 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 collect_resources @resources = [@resources] unless @resources.is_a?(Array) method = "collect_#{form.to_s}_resources" send(method) end def collect_exported_resources raise Puppet::ParseError, "realize() is not yet implemented for exported resources" end # Collect resources directly; this is the result of using 'realize', # which specifies resources, rather than using a normal collection. def collect_virtual_resources return [] unless defined?(@resources) and ! @resources.empty? result = @resources.dup.collect do |ref| if res = @scope.findresource(ref.to_s) @resources.delete(ref) res end end.reject { |r| r.nil? }.each do |res| res.virtual = false end # If there are no more resources to find, delete this from the list # of collections. @scope.compiler.delete_collection(self) if @resources.empty? result end # Collect just virtual objects, from our local compiler. def collect_virtual(exported = false) scope.compiler.resources.find_all do |resource| resource.type == @type and (exported ? resource.exported? : true) and match?(resource) end end # Does the resource match our tests? We don't yet support tests, # so it's always true at the moment. def match?(resource) if self.vquery return self.vquery.call(resource) else return true end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/compiler.rb����������������������������������������������������������0000664�0052762�0001160�00000052257�12650174557�020503� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'forwardable' require 'puppet/node' require 'puppet/resource/catalog' require 'puppet/util/errors' require 'puppet/resource/type_collection_helper' # Maintain a graph of scopes, along with a bunch of data # about the individual catalog we're compiling. class Puppet::Parser::Compiler extend Forwardable include Puppet::Util include Puppet::Util::Errors include Puppet::Util::MethodHelper include Puppet::Resource::TypeCollectionHelper def self.compile(node) $env_module_directories = nil node.environment.check_for_reparse if node.environment.conflicting_manifest_settings? errmsg = [ "The 'disable_per_environment_manifest' setting is true, and this '#{node.environment}'", "has an environment.conf manifest that conflicts with the 'default_manifest' setting.", "Compilation has been halted in order to avoid running a catalog which may be using", "unexpected manifests. For more information, see", "http://docs.puppetlabs.com/puppet/latest/reference/environments.html", ] raise(Puppet::Error, errmsg.join(' ')) end new(node).compile {|resulting_catalog| resulting_catalog.to_resource } 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 attr_reader :node, :facts, :collections, :catalog, :resources, :relationships, :topscope # The injector that provides lookup services, or nil if accessed before the compiler has started compiling and # bootstrapped. The injector is initialized and available before any manifests are evaluated. # # @return [Puppet::Pops::Binder::Injector, nil] The injector that provides lookup services for this compiler/environment # @api public # attr_accessor :injector # Access to the configured loaders for 4x # @return [Puppet::Pops::Loader::Loaders] the configured loaders # @api private attr_reader :loaders # The injector that provides lookup services during the creation of the {#injector}. # @return [Puppet::Pops::Binder::Injector, nil] The injector that provides lookup services during injector creation # for this compiler/environment # # @api private # attr_accessor :boot_injector # 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) @resources << resource # Note that this will fail if the resource is not unique. @catalog.add_resource(resource) if not resource.class? and resource[:stage] raise ArgumentError, "Only classes can set 'stage'; normal resources like #{resource} cannot change run stage" 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? return @catalog.add_edge(scope.resource, resource) end end # Do we use nodes found in the code, vs. the external node sources? def_delegator :known_resource_types, :nodes?, :ast_nodes? # Store the fact that we've evaluated a class def add_class(name) @catalog.add_class(name) unless name == "" end # Return a list of all of the defined classes. def_delegator :@catalog, :classes, :classlist # 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.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 } if is_binder_active? # create injector, if not already created - this is for 3x that does not trigger # lazy loading of injector via context Puppet::Util::Profiler.profile("Compile: Created injector", [:compiler, :create_injector]) { injector } end Puppet::Util::Profiler.profile("Compile: Evaluated main", [:compiler, :evaluate_main]) { evaluate_main } 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 generators", [:compiler, :evaluate_generators]) { evaluate_generators } Puppet::Util::Profiler.profile("Compile: Finished catalog", [:compiler, :finish_catalog]) { finish } fail_on_unevaluated if block_given? yield @catalog else @catalog end end end # Constructs the overrides for the context def context_overrides() if Puppet.future_parser?(environment) { :current_environment => environment, :global_scope => @topscope, # 4x placeholder for new global scope :loaders => lambda {|| loaders() }, # 4x loaders :injector => lambda {|| injector() } # 4x API - via context instead of via compiler } else { :current_environment => environment, } end 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 # Evaluate each specified class in turn. If there are any classes we can't # find, raise an error. This method really just creates resource objects # that point back to the classes, and then the resources are themselves # evaluated later in the process. # # Sometimes we evaluate classes with a fully qualified name already, in which # case, we tell scope.find_hostclass we've pre-qualified the name so it # doesn't need to search its namespaces again. This gets around a weird # edge case of duplicate class names, one at top scope and one nested in our # namespace and the wrong one (or both!) getting selected. See ticket #13349 # for more detail. --jeffweiss 26 apr 2012 def evaluate_classes(classes, scope, lazy_evaluate = true, fqname = false) 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 hostclasses = classes.collect do |name| scope.find_hostclass(name, :assume_fqname => fqname) or raise Puppet::Error, "Could not find class #{name} for #{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 = node set_options(options) initvars 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 def injector create_injector if @injector.nil? @injector end def loaders @loaders ||= Puppet::Pops::Loaders.new(environment) end def boot_injector create_boot_injector(nil) if @boot_injector.nil? @boot_injector end # Creates the boot injector from registered system, default, and injector config. # @return [Puppet::Pops::Binder::Injector] the created boot injector # @api private Cannot be 'private' since it is called from the BindingsComposer. # def create_boot_injector(env_boot_bindings) assert_binder_active() pb = Puppet::Pops::Binder boot_contribution = pb::SystemBindings.injector_boot_contribution(env_boot_bindings) final_contribution = pb::SystemBindings.final_contribution binder = pb::Binder.new(pb::BindingsFactory.layered_bindings(final_contribution, boot_contribution)) @boot_injector = pb::Injector.new(binder) end # Answers if Puppet Binder should be active or not, and if it should and is not active, then it is activated. # @return [Boolean] true if the Puppet Binder should be activated def is_binder_active? should_be_active = Puppet[:binder] || Puppet.future_parser? if should_be_active # TODO: this should be in a central place, not just for ParserFactory anymore... Puppet::Parser::ParserFactory.assert_rgen_installed() @@binder_loaded ||= false unless @@binder_loaded require 'puppet/pops' require 'puppetx' @@binder_loaded = true end end should_be_active 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 # If ast nodes are enabled, then see if we can find and evaluate one. def evaluate_ast_node return unless ast_nodes? # Now see if we can find the node. astnode = nil @node.names.each do |name| break if astnode = known_resource_types.node(name.to_s.downcase) end unless (astnode ||= known_resource_types.node("default")) raise Puppet::ParseError, "Could not find default node or by name with '#{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 !unevaluated_resources.each do |resource| resource.evaluate end.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 + 1}) on generators", [: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 @main = known_resource_types.find_hostclass([""], "") || known_resource_types.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? fail Puppet::ParseError, "Could not find resource(s) #{remaining.join(', ')} for overriding" end end # Make sure we don't have any remaining collections that specifically # look for resources, because we want to consider those to be # parse errors. def fail_on_unevaluated_resource_collections if Puppet.future_parser? remaining = @collections.collect(&:unresolved_resources).flatten.compact else remaining = @collections.collect(&:resources).flatten.compact end if !remaining.empty? raise Puppet::ParseError, "Failed to realize virtual 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) 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.tag(*(source.tags)) 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) # MOVED HERE - SCOPE IS NEEDED (MOVE-SCOPE) # Create the initial scope, it is needed early @topscope = Puppet::Parser::Scope.new(self) # Need to compute overrides here, and remember them, because we are about to # enter the magic zone of known_resource_types and intial 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 = known_resource_types.version 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 end # Set the node's parameters into the top-scope as variables. def set_node_parameters node.parameters.each do |param, value| @topscope[param.to_s] = value end # These might be nil. catalog.client_version = node.parameters["clientversion"] catalog.server_version = node.parameters["serverversion"] if Puppet[:trusted_node_data] @topscope.set_trusted(node.trusted_data) end if(Puppet[:immutable_node_data]) facts_hash = node.facts.nil? ? {} : node.facts.values @topscope.set_facts(facts_hash) end end def create_settings_scope settings_type = Puppet::Resource::Type.new :hostclass, "settings" environment.known_resource_types.add(settings_type) 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) env = environment Puppet.settings.each do |name, setting| next if name == :name scope[name.to_s] = env[name] end 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-circuting resources.reject { |resource| resource.evaluated? or resource.virtual? or resource.builtin_type? } end # Creates the injector from bindings found in the current environment. # @return [void] # @api private # def create_injector assert_binder_active() composer = Puppet::Pops::Binder::BindingsComposer.new() layered_bindings = composer.compose(topscope) @injector = Puppet::Pops::Binder::Injector.new(Puppet::Pops::Binder::Binder.new(layered_bindings)) end def assert_binder_active unless is_binder_active? raise ArgumentError, "The Puppet Binder is only available when either '--binder true' or '--parser future' is used" end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/e4_parser_adapter.rb�������������������������������������������������0000664�0052762�0001160�00000004353�12650174557�022247� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/pops' module Puppet; module Parser; end; end; # Adapts an egrammar/eparser to respond to the public API of the classic parser # and makes use of the new evaluator. # class Puppet::Parser::E4ParserAdapter # Empty adapter fulfills watch_file contract without doing anything. # @api private class NullFileWatcher def watch_file(file) #nop end end # @param file_watcher [#watch_file] something that can watch a file def initialize(file_watcher = nil) @file_watcher = file_watcher || NullFileWatcher.new @file = '' @string = '' @use = :unspecified @@evaluating_parser ||= Puppet::Pops::Parser::EvaluatingParser.new() end def file=(file) @file = file @use = :file # watch if possible, but only if the file is something worth watching @file_watcher.watch_file(file) if !file.nil? && file != '' end def parse(string = nil) self.string= string if string if @file =~ /\.rb$/ && @use != :string # Will throw an error parse_ruby_file end parse_result = 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. # @@evaluating_parser.parse_string(@string, @file || "unknown-source-location") else @@evaluating_parser.parse_file(@file) end # the parse_result may be # * empty / nil (no input) # * a Model::Program # * a Model::Expression # model = parse_result.nil? ? nil : parse_result.current args = {} Puppet::Pops::Model::AstTransformer.new(@file).merge_location(args, model) ast_code = if model.is_a? Puppet::Pops::Model::Program Puppet::Parser::AST::PopsBridge::Program.new(model, args) else args[:value] = model Puppet::Parser::AST::PopsBridge::Expression.new(args) end # Create the "main" class for the content - this content will get merged with all other "main" content Puppet::Parser::AST::Hostclass.new('', :code => ast_code) end def string=(string) @string = string @use = :string end def parse_ruby_file raise Puppet::ParseError, "Ruby DSL is no longer supported. Attempt to parse #{@file}" end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/files.rb�������������������������������������������������������������0000664�0052762�0001160�00000010334�12650174557�017761� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/module' 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) begin if mod = environment.module(module_name) return [mod.name, mod.match_manifests(file_pattern)] end rescue Puppet::Module::InvalidName # one of the modules being loaded might have an invalid name and so # looking for one might blow up since we load them lazily. end [nil, []] 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) if Puppet::Util.absolute_path?(file) file else path, module_file = split_file_path(file) mod = environment.module(path) if module_file && mod mod.file(module_file) else nil end end end # Find the path to the given template selector. Templates can be selected in # a number of ways: # * absolute path: the path is simply returned # * path relative to the templatepath setting: a file is found and the path # is 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) if Puppet::Util.absolute_path?(template) template else in_templatepath = find_template_in_templatepath(template, environment) if in_templatepath in_templatepath else find_template_in_module(template, environment) end end end # Templatepaths are deprecated functionality, this will be going away in # Puppet 4. # # @api private def find_template_in_templatepath(template, environment) template_paths = templatepath(environment) if template_paths template_paths.collect do |path| File::join(path, template) end.find do |f| Puppet::FileSystem.exist?(f) end else nil end end # @api private def find_template_in_module(template, environment) path, file = split_file_path(template) mod = environment.module(path) if file && mod mod.template(file) else nil end end # Return an array of paths by splitting the +templatedir+ config # parameter. # @api private def templatepath(environment) dirs = Puppet.settings.value(:templatedir, environment.to_s).split(File::PATH_SEPARATOR) dirs.select do |p| File::directory?(p) 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-3.8.5/lib/puppet/parser/functions.rb���������������������������������������������������������0000664�0052762�0001160�00000021730�12650174557�020671� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/autoload' require 'puppet/parser/scope' # 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 @modules = {} # Runs a newfunction to create a function for each of the log levels Puppet::Util::Log.levels.each do |level| newfunction(level, :environment => Puppet.lookup(:root_environment), :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", :wrap => false ) end # Get the module that functions are mixed into corresponding to an # environment # # @api private def self.environment_module(env) @modules[env.name] ||= 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 # 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](http://docs.puppetlabs.com/guides/custom_functions.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](http://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}" 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 #{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}", [: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 (#{args[0].size} for #{arity})" elsif arity < 0 and args[0].size < (arity+1).abs raise ArgumentError, "#{name}(): Wrong number of arguments given (#{args[0].size} for minimum #{(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 func = nil 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 end ����������������������������������������puppet-3.8.5/lib/puppet/parser/functions/�����������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020340� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/assert_type.rb���������������������������������������������0000664�0052762�0001160�00000002020�12650174557�023222� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :assert_type, :type => :rvalue, :arity => -3, :doc => "Returns the given value if it is an instance of the given type, and raises an error otherwise. Optionally, if a block is given (accepting two parameters), it will be called instead of raising an error. This to enable giving the user richer feedback, or to supply a default value. Example: assert that `$b` is a non empty `String` and assign to `$a`: $a = assert_type(String[1], $b) Example using custom error message: $a = assert_type(String[1], $b) |$expected, $actual| { fail('The name cannot be empty') } Example, using a warning and a default: $a = assert_type(String[1], $b) |$expected, $actual| { warning('Name is empty, using default') 'anonymous' } See the documentation for 'The Puppet Type System' for more information about types. - since Puppet 3.7 - requires future parser/evaluator ") do |args| function_fail(["assert_type() is only available when parser/evaluator future is in effect"]) end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/contain.rb�������������������������������������������������0000664�0052762�0001160�00000002746�12650174557�022332� 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. When the future parser is used, you must use the class's full name; relative names are no longer 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. " ) do |classes| scope = self # Make call patterns uniform and protected against nested arrays, also make # names absolute if so desired. classes = transform_and_assert_classnames(classes.is_a?(Array) ? classes.flatten : [classes]) containing_resource = scope.resource # This is the same as calling the include function but faster and does not rely on the include # function (which is a statement) to return something (it should not). (compiler.evaluate_classes(classes, self, false) || []).each do |resource| if ! scope.catalog.edge?(containing_resource, resource) scope.catalog.add_edge(containing_resource, resource) end end end ��������������������������puppet-3.8.5/lib/puppet/parser/functions/create_resources.rb����������������������������������������0000664�0052762�0001160�00000005356�12650174557�024234� 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) ENDHEREDOC raise ArgumentError, ("create_resources(): wrong number of arguments (#{args.length}; must be 2 or 3)") 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 ||= {} resource = Puppet::Parser::AST::Resource.new(:type => type.sub(/^@{1,2}/, '').downcase, :instances => instances.collect do |title, params| Puppet::Parser::AST::ResourceInstance.new( :title => Puppet::Parser::AST::Leaf.new(:value => title), :parameters => defaults.merge(params).collect do |name, value| Puppet::Parser::AST::ResourceParam.new( :param => name, :value => Puppet::Parser::AST::Leaf.new(:value => value)) end) end) if type.start_with? '@@' resource.exported = true elsif type.start_with? '@' resource.virtual = true end resource.safeevaluate(self) end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/defined.rb�������������������������������������������������0000664�0052762�0001160�00000007744�12650174557�022300� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Test whether a given class or definition is defined Puppet::Parser::Functions::newfunction(:defined, :type => :rvalue, :arity => -2, :doc => "Determine whether a given class or resource type is defined. This function can also determine whether a specific resource has been declared, or whether a variable has been assigned a value (including undef...as opposed to never having been assigned anything). Returns true or false. Accepts class names, type names, resource references, and variable reference strings of the form '$name'. When more than one argument is supplied, defined() returns true if any are defined. The `defined` function checks both native and defined types, including types provided as plugins via modules. Types and classes are both checked using their names: defined(\"file\") defined(\"customtype\") defined(\"foo\") defined(\"foo::bar\") defined(\'$name\') Resource declarations are checked using resource references, e.g. `defined( File['/tmp/myfile'] )`. Checking whether a given resource has been declared is, unfortunately, dependent on the parse order of the configuration, and the following code will not work: if defined(File['/tmp/foo']) { notify { \"This configuration includes the /tmp/foo file.\":} } file { \"/tmp/foo\": ensure => present, } However, this order requirement refers to parse order only, and ordering of resources in the configuration graph (e.g. with `before` or `require`) does not affect the behavior of `defined`. If the future parser is in effect, you may also search using types: defined(Resource[\'file\',\'/some/file\']) defined(File[\'/some/file\']) defined(Class[\'foo\']) The `defined` function does not answer if 4.x data types (e.g. `Integer`) are defined. If given the string 'integer' the result is false, and if given a non CatalogEntry type, an error is raised. The rules for asking for undef, empty strings, and the main class are different from 3.x (non future parser) and 4.x (with future parser or in Puppet 4.0.0 and later): defined('') # 3.x => true, 4.x => false defined(undef) # 3.x => true, 4.x => error defined('main') # 3.x => false, 4.x => true With the future parser, it is also possible to ask specifically if a name is a resource type (built in or defined), or a class, by giving its type: defined(Type[Class['foo']]) defined(Type[Resource['foo']]) Which is different from asking: defined('foo') Since the later returns true if 'foo' is either a class, a built-in resource type, or a user defined resource type, and a specific request like `Type[Class['foo']]` only returns true if `'foo'` is a class. - Since 2.7.0 - Since 3.6.0 variable reference and future parser types - Since 3.8.1 type specific requests with future parser") do |vals| vals = [vals] unless vals.is_a?(Array) vals.any? do |val| case val when String if m = /^\$(.+)$/.match(val) exist?(m[1]) else find_resource_type(val) or find_definition(val) or find_hostclass(val) end when Puppet::Resource compiler.findresource(val.type, val.title) else if Puppet.future_parser? case val when Puppet::Pops::Types::PResourceType raise ArgumentError, "The given resource type is a reference to all kind of types" if val.type_name.nil? if val.title.nil? find_builtin_resource_type(val.type_name) || find_definition(val.type_name) else compiler.findresource(val.type_name, val.title) end when Puppet::Pops::Types::PHostClassType raise ArgumentError, "The given class type is a reference to all classes" if val.class_name.nil? find_hostclass(val.class_name) end else raise ArgumentError, "Invalid argument of type '#{val.class}' to 'defined'" end end end end ����������������������������puppet-3.8.5/lib/puppet/parser/functions/digest.rb��������������������������������������������������0000664�0052762�0001160�00000000522�12650174557�022144� 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-3.8.5/lib/puppet/parser/functions/each.rb����������������������������������������������������0000664�0052762�0001160�00000004247�12650174557�021575� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :each, :type => :rvalue, :arity => -3, :doc => <<-DOC Applies a parameterized block to each element in a sequence of selected entries from the first argument and returns the first argument. This function takes two mandatory arguments: the first should be an Array or a Hash or something that is of enumerable type (integer, Integer range, or String), and the second a parameterized block as produced by the puppet syntax: $a.each |$x| { ... } each($a) |$x| { ... } When the first argument is an Array (or of enumerable type other than Hash), the parameterized block should define one or two block parameters. For each application of the block, the next element from the array is selected, and it is passed to the block if the block has one parameter. If the block has two parameters, the first is the elements index, and the second the value. The index starts from 0. $a.each |$index, $value| { ... } each($a) |$index, $value| { ... } When the first argument is a Hash, the parameterized block should define one or two parameters. When one parameter is defined, the iteration is performed with each entry as an array of `[key, value]`, and when two parameters are defined the iteration is performed with key and value. $a.each |$entry| { ..."key ${$entry[0]}, value ${$entry[1]}" } $a.each |$key, $value| { ..."key ${key}, value ${value}" } Example using each: [1,2,3].each |$val| { ... } # 1, 2, 3 [5,6,7].each |$index, $val| { ... } # (0, 5), (1, 6), (2, 7) {a=>1, b=>2, c=>3}].each |$val| { ... } # ['a', 1], ['b', 2], ['c', 3] {a=>1, b=>2, c=>3}.each |$key, $val| { ... } # ('a', 1), ('b', 2), ('c', 3) Integer[ 10, 20 ].each |$index, $value| { ... } # (0, 10), (1, 11) ... "hello".each |$char| { ... } # 'h', 'e', 'l', 'l', 'o' 3.each |$number| { ... } # 0, 1, 2 - since 3.2 for Array and Hash - since 3.5 for other enumerables - note requires `parser = future` DOC ) do |args| function_fail(["each() is only available when parser/evaluator future is in effect"]) end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/epp.rb�����������������������������������������������������0000664�0052762�0001160�00000005270�12650174557�021456� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction(:epp, :type => :rvalue, :arity => -2, :doc => "Evaluates an Embedded Puppet Template (EPP) file and returns the rendered text result as a String. The first 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.epp` will load the file `<MODULES DIRECTORY>/apache/templates/vhost.conf.epp`.) The second argument is optional; if present, it should be a hash containing parameters for the template. (See below.) EPP supports the following tags: * `<%= puppet expression %>` - This tag renders the value of the expression it contains. * `<% puppet expression(s) %>` - This tag will execute the expression(s) it contains, but renders nothing. * `<%# comment %>` - The tag and its content renders nothing. * `<%%` or `%%>` - Renders a literal `<%` or `%>` respectively. * `<%-` - Same as `<%` but suppresses any leading whitespace. * `-%>` - Same as `%>` but suppresses any trailing whitespace on the same line (including line break). * `<%- |parameters| -%>` - When placed as the first tag declares the template's parameters. File based EPP supports the following visibilities of variables in scope: * Global scope (i.e. top + node scopes) - global scope is always visible * Global + all given arguments - if the EPP template does not declare parameters, and arguments are given * Global + declared parameters - if the EPP declares parameters, given argument names must match EPP supports parameters by placing an optional parameter list as the very first element in the EPP. As an example, `<%- |$x, $y, $z = 'unicorn'| -%>` when placed first in the EPP text declares that the parameters `x` and `y` must be given as template arguments when calling `inline_epp`, and that `z` if not given as a template argument defaults to `'unicorn'`. Template parameters are available as variables, e.g.arguments `$x`, `$y` and `$z` in the example. Note that `<%-` must be used or any leading whitespace will be interpreted as text Arguments are passed to the template by calling `epp` with a Hash as the last argument, where parameters are bound to values, e.g. `epp('...', {'x'=>10, 'y'=>20})`. Excess arguments may be given (i.e. undeclared parameters) only if the EPP templates does not declare any parameters at all. Template parameters shadow variables in outer scopes. File based epp does never have access to variables in the scope where the `epp` function is called from. - See function inline_epp for examples of EPP - Since 3.5 - Requires Future Parser") do |args| function_fail(["epp() is only available when parser/evaluator future is in effect"]) end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/extlookup.rb�����������������������������������������������0000664�0052762�0001160�00000013213�12650174557�022720� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'csv' module Puppet::Parser::Functions newfunction(:extlookup, :type => :rvalue, :arity => -2, :doc => "This is a parser function to read data from external files, this version uses CSV files but the concept can easily be adjust for databases, yaml or any other queryable data source. The object of this is to make it obvious when it's being used, rather than magically loading data in when a module is loaded I prefer to look at the code and see statements like: $snmp_contact = extlookup(\"snmp_contact\") The above snippet will load the snmp_contact value from CSV files, this in its own is useful but a common construct in puppet manifests is something like this: case $domain { \"myclient.com\": { $snmp_contact = \"John Doe <john@myclient.com>\" } default: { $snmp_contact = \"My Support <support@my.com>\" } } Over time there will be a lot of this kind of thing spread all over your manifests and adding an additional client involves grepping through manifests to find all the places where you have constructs like this. This is a data problem and shouldn't be handled in code, and using this function you can do just that. First you configure it in site.pp: $extlookup_datadir = \"/etc/puppet/manifests/extdata\" $extlookup_precedence = [\"%{fqdn}\", \"domain_%{domain}\", \"common\"] The array tells the code how to resolve values, first it will try to find it in web1.myclient.com.csv then in domain_myclient.com.csv and finally in common.csv Now create the following data files in /etc/puppet/manifests/extdata: domain_myclient.com.csv: snmp_contact,John Doe <john@myclient.com> root_contact,support@%{domain} client_trusted_ips,192.168.1.130,192.168.10.0/24 common.csv: snmp_contact,My Support <support@my.com> root_contact,support@my.com Now you can replace the case statement with the simple single line to achieve the exact same outcome: $snmp_contact = extlookup(\"snmp_contact\") The above code shows some other features, you can use any fact or variable that is in scope by simply using %{varname} in your data files, you can return arrays by just having multiple values in the csv after the initial variable name. In the event that a variable is nowhere to be found a critical error will be raised that will prevent your manifest from compiling, this is to avoid accidentally putting in empty values etc. You can however specify a default value: $ntp_servers = extlookup(\"ntp_servers\", \"1.${country}.pool.ntp.org\") In this case it will default to \"1.${country}.pool.ntp.org\" if nothing is defined in any data file. You can also specify an additional data file to search first before any others at use time, for example: $version = extlookup(\"rsyslog_version\", \"present\", \"packages\") package{\"rsyslog\": ensure => $version } This will look for a version configured in packages.csv and then in the rest as configured by $extlookup_precedence if it's not found anywhere it will default to `present`, this kind of use case makes puppet a lot nicer for managing large amounts of packages since you do not need to edit a load of manifests to do simple things like adjust a desired version number. Precedence values can have variables embedded in them in the form %{fqdn}, you could for example do: $extlookup_precedence = [\"hosts/%{fqdn}\", \"common\"] This will result in /path/to/extdata/hosts/your.box.com.csv being searched. This is for back compatibility to interpolate variables with %. % interpolation is a workaround for a problem that has been fixed: Puppet variable interpolation at top scope used to only happen on each run.") do |args| key = args[0] default = args[1] datafile = args[2] raise ArgumentError, ("extlookup(): wrong number of arguments (#{args.length}; must be <= 3)") if args.length > 3 extlookup_datadir = undef_as('',self['::extlookup_datadir']) extlookup_precedence = undef_as([],self['::extlookup_precedence']).collect { |var| var.gsub(/%\{(.+?)\}/) { self["::#{$1}"] } } datafiles = Array.new # if we got a custom data file, put it first in the array of search files if datafile != "" datafiles << extlookup_datadir + "/#{datafile}.csv" if Puppet::FileSystem.exist?(extlookup_datadir + "/#{datafile}.csv") end extlookup_precedence.each do |d| datafiles << extlookup_datadir + "/#{d}.csv" end desired = nil datafiles.each do |file| if desired.nil? if Puppet::FileSystem.exist?(file) result = CSV.read(file).find_all do |r| r[0] == key end # return just the single result if theres just one, # else take all the fields in the csv and build an array if result.length > 0 if result[0].length == 2 val = result[0][1].to_s # parse %{}'s in the CSV into local variables using the current scope while val =~ /%\{(.+?)\}/ val.gsub!(/%\{#{$1}\}/, self[$1]) end desired = val elsif result[0].length > 1 length = result[0].length cells = result[0][1,length] # Individual cells in a CSV result are a weird data type and throws # puppets yaml parsing, so just map it all to plain old strings desired = cells.map do |c| # parse %{}'s in the CSV into local variables using the current scope while c =~ /%\{(.+?)\}/ c.gsub!(/%\{#{$1}\}/, self[$1]) end c.to_s end end end end end end desired || default or raise Puppet::ParseError, "No match found for '#{key}' in any data file during extlookup()" end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/fail.rb����������������������������������������������������0000664�0052762�0001160�00000000333�12650174557�021600� 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-3.8.5/lib/puppet/parser/functions/file.rb����������������������������������������������������0000664�0052762�0001160�00000002074�12650174557�021610� 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 #{vals.join(", ")}" end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/filter.rb��������������������������������������������������0000664�0052762�0001160�00000003424�12650174557�022156� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :filter, :arity => -3, :doc => <<-DOC Applies a parameterized block to each element in a sequence of entries from the first argument and returns an array or hash (same type as left operand for array/hash, and array for other enumerable types) with the entries for which the block evaluates to `true`. This function takes two mandatory arguments: the first should be an Array, a Hash, or an Enumerable object (integer, Integer range, or String), and the second a parameterized block as produced by the puppet syntax: $a.filter |$x| { ... } filter($a) |$x| { ... } When the first argument is something other than a Hash, the block is called with each entry in turn. When the first argument is a Hash the entry is an array with `[key, value]`. Example Using filter with one parameter # selects all that end with berry $a = ["raspberry", "blueberry", "orange"] $a.filter |$x| { $x =~ /berry$/ } # rasberry, blueberry If the block defines two parameters, they will be set to `index, value` (with index starting at 0) for all enumerables except Hash, and to `key, value` for a Hash. Example Using filter with two parameters # selects all that end with 'berry' at an even numbered index $a = ["raspberry", "blueberry", "orange"] $a.filter |$index, $x| { $index % 2 == 0 and $x =~ /berry$/ } # raspberry # selects all that end with 'berry' and value >= 1 $a = {"raspberry"=>0, "blueberry"=>1, "orange"=>1} $a.filter |$key, $x| { $x =~ /berry$/ and $x >= 1 } # blueberry - since 3.4 for Array and Hash - since 3.5 for other enumerables - note requires `parser = future` DOC ) do |args| function_fail(["filter() is only available when parser/evaluator future is in effect"]) end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/fqdn_rand.rb�����������������������������������������������0000664�0052762�0001160�00000002263�12650174557�022625� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'digest/md5' 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 whole 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 seed = Digest::MD5.hexdigest([self['::fqdn'],args].join(':')).hex Puppet::Util.deterministic_rand(seed,max) end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/generate.rb������������������������������������������������0000664�0052762�0001160�00000003141�12650174557�022457� 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| 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 #{args[0]}: #{detail}", detail.backtrace end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/hiera.rb���������������������������������������������������0000664�0052762�0001160�00000003012�12650174557�021752� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'hiera_puppet' module Puppet::Parser::Functions newfunction(:hiera, :type => :rvalue, :arity => -2, :doc => "Performs a standard priority lookup and returns the most specific value for a given key. The returned value can be data of any type (strings, arrays, or hashes) The function can be called in one of three ways: 1. Using 1 to 3 arguments where the arguments are: 'key' [String] Required The key to lookup. 'default` [Any] Optional A value to return when there's no match for `key`. Optional `override` [Any] Optional An argument in the third position, providing a data source to consult for matching values, even if it would not ordinarily be part of the matched hierarchy. If Hiera doesn't find a matching key in the named override data source, it will continue to search through the rest of the hierarchy. 2. Using a 'key' and an optional 'override' parameter like in #1 but with a block to provide the default value. The block is called with one parameter (the key) and should return the value. This option can only be used with the 3x future parser or from 4.0.0. 3. Like #1 but with all arguments passed in an array. More thorough examples of `hiera` are available at: <http://docs.puppetlabs.com/hiera/1/puppet.html#hiera-lookup-functions> ") do |*args| key, default, override = HieraPuppet.parse_args(args) HieraPuppet.lookup(key, default, self, override, :priority) end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/hiera_array.rb���������������������������������������������0000664�0052762�0001160�00000003204�12650174557�023153� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'hiera_puppet' module Puppet::Parser::Functions newfunction(:hiera_array, :type => :rvalue, :arity => -2,:doc => "Returns all matches throughout the hierarchy --- not just the first match --- as a flattened array of unique values. If any of the matched values are arrays, they're flattened and included in the results. The function can be called in one of three ways: 1. Using 1 to 3 arguments where the arguments are: 'key' [String] Required The key to lookup. 'default` [Any] Optional A value to return when there's no match for `key`. Optional `override` [Any] Optional An argument in the third position, providing a data source to consult for matching values, even if it would not ordinarily be part of the matched hierarchy. If Hiera doesn't find a matching key in the named override data source, it will continue to search through the rest of the hierarchy. 2. Using a 'key' and an optional 'override' parameter like in #1 but with a block to provide the default value. The block is called with one parameter (the key) and should return the value. This option can only be used with the 3x future parser or from 4.0.0. 3. Like #1 but with all arguments passed in an array. If any matched value is a hash, puppet will raise a type mismatch error. More thorough examples of `hiera` are available at: <http://docs.puppetlabs.com/hiera/1/puppet.html#hiera-lookup-functions> ") do |*args| key, default, override = HieraPuppet.parse_args(args) HieraPuppet.lookup(key, default, self, override, :array) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/hiera_hash.rb����������������������������������������������0000664�0052762�0001160�00000003445�12650174557�022767� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'hiera_puppet' module Puppet::Parser::Functions newfunction(:hiera_hash, :type => :rvalue, :arity => -2, :doc => "Returns a merged hash of matches from throughout the hierarchy. In cases where two or more hashes share keys, the hierarchy order determines which key/value pair will be used in the returned hash, with the pair in the highest priority data source winning. The function can be called in one of three ways: 1. Using 1 to 3 arguments where the arguments are: 'key' [String] Required The key to lookup. 'default` [Any] Optional A value to return when there's no match for `key`. Optional `override` [Any] Optional An argument in the third position, providing a data source to consult for matching values, even if it would not ordinarily be part of the matched hierarchy. If Hiera doesn't find a matching key in the named override data source, it will continue to search through the rest of the hierarchy. 2. Using a 'key' and an optional 'override' parameter like in #1 but with a block to provide the default value. The block is called with one parameter (the key) and should return the value. This option can only be used with the 3x future parser or from 4.0.0. 3. Like #1 but with all arguments passed in an array. `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 will raise a type mismatch error. More thorough examples of `hiera_hash` are available at: <http://docs.puppetlabs.com/hiera/1/puppet.html#hiera-lookup-functions> ") do |*args| key, default, override = HieraPuppet.parse_args(args) HieraPuppet.lookup(key, default, self, override, :hash) end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/hiera_include.rb�������������������������������������������0000664�0052762�0001160�00000004405�12650174557�023464� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'hiera_puppet' module Puppet::Parser::Functions newfunction(:hiera_include, :arity => -2, :doc => "Assigns classes to a node using an array merge lookup that retrieves the value for a user-specified key from a Hiera data source. To use `hiera_include`, the following configuration is required: - A key name to use for classes, e.g. `classes`. - A line in the puppet `sites.pp` file (e.g. `/etc/puppet/manifests/sites.pp`) reading `hiera_include('classes')`. Note that this line must be outside any node definition and below any top-scope variables in use for Hiera lookups. - Class keys in the appropriate data sources. In a data source keyed to a node's role, one might have: --- classes: - apache - apache::passenger The function can be called in one of three ways: 1. Using 1 to 3 arguments where the arguments are: 'key' [String] Required The key to lookup. 'default` [Any] Optional A value to return when there's no match for `key`. Optional `override` [Any] Optional An argument in the third position, providing a data source to consult for matching values, even if it would not ordinarily be part of the matched hierarchy. If Hiera doesn't find a matching key in the named override data source, it will continue to search through the rest of the hierarchy. 2. Using a 'key' and an optional 'override' parameter like in #1 but with a block to provide the default value. The block is called with one parameter (the key) and should return the array to be used in the subsequent call to include. This option can only be used with the 3x future parser or from 4.0.0. 3. Like #1 but with all arguments passed in an array. More thorough examples of `hiera_include` are available at: <http://docs.puppetlabs.com/hiera/1/puppet.html#hiera-lookup-functions> ") do |*args| key, default, override = HieraPuppet.parse_args(args) if answer = HieraPuppet.lookup(key, default, self, override, :array) method = Puppet::Parser::Functions.function(:include) send(method, [answer]) else raise Puppet::ParseError, "Could not find data item #{key}" end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/include.rb�������������������������������������������������0000664�0052762�0001160�00000003223�12650174557�022311� 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. When the future parser is used, you must use the class's full name; relative names are no longer 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. ") do |vals| # Unify call patterns (if called with nested arrays), make names absolute if # wanted and evaluate the classes compiler.evaluate_classes( transform_and_assert_classnames( vals.is_a?(Array) ? vals.flatten : [vals]), self, false) end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/inline_epp.rb����������������������������������������������0000664�0052762�0001160�00000006746�12650174557�023025� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction(:inline_epp, :type => :rvalue, :arity => -2, :doc => "Evaluates an Embedded Puppet Template (EPP) string and returns the rendered text result as a String. EPP support the following tags: * `<%= puppet expression %>` - This tag renders the value of the expression it contains. * `<% puppet expression(s) %>` - This tag will execute the expression(s) it contains, but renders nothing. * `<%# comment %>` - The tag and its content renders nothing. * `<%%` or `%%>` - Renders a literal `<%` or `%>` respectively. * `<%-` - Same as `<%` but suppresses any leading whitespace. * `-%>` - Same as `%>` but suppresses any trailing whitespace on the same line (including line break). * `<%- |parameters| -%>` - When placed as the first tag declares the template's parameters. Inline EPP supports the following visibilities of variables in scope which depends on how EPP parameters are used - see further below: * Global scope (i.e. top + node scopes) - global scope is always visible * Global + Enclosing scope - if the EPP template does not declare parameters, and no arguments are given * Global + all given arguments - if the EPP template does not declare parameters, and arguments are given * Global + declared parameters - if the EPP declares parameters, given argument names must match EPP supports parameters by placing an optional parameter list as the very first element in the EPP. As an example, `<%- |$x, $y, $z='unicorn'| -%>` when placed first in the EPP text declares that the parameters `x` and `y` must be given as template arguments when calling `inline_epp`, and that `z` if not given as a template argument defaults to `'unicorn'`. Template parameters are available as variables, e.g.arguments `$x`, `$y` and `$z` in the example. Note that `<%-` must be used or any leading whitespace will be interpreted as text Arguments are passed to the template by calling `inline_epp` with a Hash as the last argument, where parameters are bound to values, e.g. `inline_epp('...', {'x'=>10, 'y'=>20})`. Excess arguments may be given (i.e. undeclared parameters) only if the EPP templates does not declare any parameters at all. Template parameters shadow variables in outer scopes. Note: An inline template is best stated using a single-quoted string, or a heredoc since a double-quoted string is subject to expression interpolation before the string is parsed as an EPP template. Here are examples (using heredoc to define the EPP text): # produces 'Hello local variable world!' $x ='local variable' inline_epptemplate(@(END:epp)) <%- |$x| -%> Hello <%= $x %> world! END # produces 'Hello given argument world!' $x ='local variable world' inline_epptemplate(@(END:epp), { x =>'given argument'}) <%- |$x| -%> Hello <%= $x %> world! END # produces 'Hello given argument world!' $x ='local variable world' inline_epptemplate(@(END:epp), { x =>'given argument'}) <%- |$x| -%> Hello <%= $x %>! END # results in error, missing value for y $x ='local variable world' inline_epptemplate(@(END:epp), { x =>'given argument'}) <%- |$x, $y| -%> Hello <%= $x %>! END # Produces 'Hello given argument planet' $x ='local variable world' inline_epptemplate(@(END:epp), { x =>'given argument'}) <%- |$x, $y=planet| -%> Hello <%= $x %> <%= $y %>! END - Since 3.5 - Requires Future Parser") do |arguments| function_fail(["inline_epp() is only available when parser/evaluator future is in effect"]) end ��������������������������puppet-3.8.5/lib/puppet/parser/functions/inline_template.rb�����������������������������������������0000664�0052762�0001160�00000001437�12650174557�024044� 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](http://docs.puppetlabs.com/guides/templating.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| 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.backtrace end end.join("") end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/lookup.rb��������������������������������������������������0000664�0052762�0001160�00000015534�12650174557�022207� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions.newfunction(:lookup, :type => :rvalue, :arity => -2, :doc => <<-'ENDHEREDOC') do |args| Looks up data defined using Puppet Bindings and Hiera. The function is callable with one to three arguments and optionally with a code block to further process the result. The lookup function can be called in one of these ways: lookup(name) lookup(name, type) lookup(name, type, default) lookup(options_hash) lookup(name, options_hash) The function may optionally be called with a code block / lambda with the following signatures: lookup(...) |$result| { ... } lookup(...) |$name, $result| { ... } lookup(...) |$name, $result, $default| { ... } The longer signatures are useful when the block needs to raise an error (it can report the name), or if it needs to know if the given default value was selected. The code block receives the following three arguments: * The `$name` is the last name that was looked up (*the* name if only one name was looked up) * The `$result` is the looked up value (or the default value if not found). * The `$default` is the given default value (`undef` if not given). The block, if present, is called with the result from the lookup. The value produced by the block is also what is produced by the `lookup` function. When a block is used, it is the users responsibility to call `error` if the result does not meet additional criteria, or if an undef value is not acceptable. If a value is not found, and a default has been specified, the default value is given to the block. The content of the options hash is: * `name` - The name or array of names to lookup (first found is returned) * `type` - The type to assert (a Type or a type specification in string form) * `default` - The default value if there was no value found (must comply with the data type) * `accept_undef` - (default `false`) An `undef` result is accepted if this options is set to `true`. * `override` - a hash with map from names to values that are used instead of the underlying bindings. If the name is found here it wins. Defaults to an empty hash. * `extra` - a hash with map from names to values that are used as a last resort to obtain a value. Defaults to an empty hash. When the call is on the form `lookup(name, options_hash)`, or `lookup(name, type, options_hash)`, the given name argument wins over the `options_hash['name']`. The search order is `override` (if given), then `binder`, then `hiera` and finally `extra` (if given). The first to produce a value other than undef for a given name wins. The type specification is one of: * A type in the Puppet Type System, e.g.: * `Integer`, an integral value with optional range e.g.: * `Integer[0, default]` - 0 or positive * `Integer[default, -1]` - negative, * `Integer[1,100]` - value between 1 and 100 inclusive * `String`- any string * `Float` - floating point number (same signature as for Integer for `Integer` ranges) * `Boolean` - true of false (strict) * `Array` - an array (of Data by default), or parameterized as `Array[<element_type>]`, where `<element_type>` is the expected type of elements * `Hash`, - a hash (of default `Literal` keys and `Data` values), or parameterized as `Hash[<value_type>]`, `Hash[<key_type>, <value_type>]`, where `<key_type>`, and `<value_type>` are the types of the keys and values respectively (key is `Literal` by default). * `Data` - abstract type representing any `Literal`, `Array[Data]`, or `Hash[Literal, Data]` * `Pattern[<p1>, <p2>, ..., <pn>]` - an enumeration of valid patterns (one or more) where a pattern is a regular expression string or regular expression, e.g. `Pattern['.com$', '.net$']`, `Pattern[/[a-z]+[0-9]+/]` * `Enum[<s1>, <s2>, ..., <sn>]`, - an enumeration of exact string values (one or more) e.g. `Enum[blue, red, green]`. * `Variant[<t1>, <t2>,...<tn>]` - matches one of the listed types (at least one must be given) e.g. `Variant[Integer[8000,8999], Integer[20000, 99999]]` to accept a value in either range * `Regexp`- a regular expression (i.e. the result is a regular expression, not a string matching a regular expression). * A string containing a type description - one of the types as shown above but in string form. If the function is called without specifying a default value, and nothing is bound to the given name an error is raised unless the option `accept_undef` is true. If a block is given it must produce an acceptable value (or call `error`). If the block does not produce an acceptable value an error is raised. Examples: When called with one argument; **the name**, it returns the bound value with the given name after having asserted it has the default datatype `Data`: lookup('the_name') When called with two arguments; **the name**, and **the expected type**, it returns the bound value with the given name after having asserted it has the given data type ('String' in the example): lookup('the_name', 'String') # 3.x lookup('the_name', String) # parser future When called with three arguments, **the name**, the **expected type**, and a **default**, it returns the bound value with the given name, or the default after having asserted the value has the given data type (`String` in the example above): lookup('the_name', 'String', 'Fred') # 3x lookup('the_name', String, 'Fred') # parser future Using a lambda to process the looked up result - asserting that it starts with an upper case letter: # only with parser future lookup('the_size', Integer[1,100]) |$result| { if $large_value_allowed and $result > 10 { error 'Values larger than 10 are not allowed'} $result } Including the name in the error # only with parser future lookup('the_size', Integer[1,100]) |$name, $result| { if $large_value_allowed and $result > 10 { error 'The bound value for '${name}' can not be larger than 10 in this configuration'} $result } When using a block, the value it produces is also asserted against the given type, and it may not be `undef` unless the option `'accept_undef'` is `true`. All options work as the corresponding (direct) argument. The `first_found` option and `accept_undef` are however only available as options. Using first_found semantics option to return the first name that has a bound value: lookup(['apache::port', 'nginx::port'], 'Integer', 80) If you want to make lookup return undef when no value was found instead of raising an error: $are_you_there = lookup('peekaboo', { accept_undef => true} ) $are_you_there = lookup('peekaboo', { accept_undef => true}) |$result| { $result } ENDHEREDOC unless Puppet[:binder] || Puppet.future_parser? raise Puppet::ParseError, "The lookup function is only available with settings --binder true, or --parser future" end Puppet::Pops::Binder::Lookup.lookup(self, args) end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/map.rb�����������������������������������������������������0000664�0052762�0001160�00000003013�12650174557�021440� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :map, :type => :rvalue, :arity => -3, :doc => <<-DOC Applies a parameterized block to each element in a sequence of entries from the first argument and returns an array with the result of each invocation of the parameterized block. This function takes two mandatory arguments: the first should be an Array, Hash, or of Enumerable type (integer, Integer range, or String), and the second a parameterized block as produced by the puppet syntax: $a.map |$x| { ... } map($a) |$x| { ... } When the first argument `$a` is an Array or of enumerable type, the block is called with each entry in turn. When the first argument is a hash the entry is an array with `[key, value]`. Example Using map with two arguments # Turns hash into array of values $a.map |$x|{ $x[1] } # Turns hash into array of keys $a.map |$x| { $x[0] } When using a block with 2 parameters, the element's index (starting from 0) for an array, and the key for a hash is given to the block's first parameter, and the value is given to the block's second parameter.args. Example Using map with two arguments # Turns hash into array of values $a.map |$key,$val|{ $val } # Turns hash into array of keys $a.map |$key,$val|{ $key } - since 3.4 for Array and Hash - since 3.5 for other enumerables, and support for blocks with 2 parameters - note requires `parser = future` DOC ) do |args| function_fail(["map() is only available when parser/evaluator future is in effect"]) end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/match.rb���������������������������������������������������0000664�0052762�0001160�00000001465�12650174557�021770� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :match, :arity => 2, :doc => <<-DOC Returns the match result of matching a String or Array[String] with one of: * Regexp * String - transformed to a Regexp * Pattern type * Regexp type Returns An Array with the entire match at index 0, and each subsequent submatch at index 1-n. If there was no match `undef` is returned. If the value to match is an Array, a array with mapped match results is returned. Example matching: "abc123".match(/([a-z]+)[1-9]+/) # => ["abc"] "abc123".match(/([a-z]+)([1-9]+)/) # => ["abc", "123"] See the documentation for "The Puppet Type System" for more information about types. - since 3.7.0 - note requires future parser DOC ) do |args| function_fail(["match() is only available when parser/evaluator future is in effect"]) end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/md5.rb�����������������������������������������������������0000664�0052762�0001160�00000000321�12650174557�021347� 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-3.8.5/lib/puppet/parser/functions/realize.rb�������������������������������������������������0000664�0052762�0001160�00000001502�12650174557�022317� 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| vals = [vals] unless vals.is_a?(Array) if Puppet.future_parser? coll = Puppet::Pops::Evaluator::Collectors::FixedSetCollector.new(self, vals.flatten) else coll = Puppet::Parser::Collector.new(self, :nomatter, nil, nil, :virtual) coll.resources = vals.flatten end compiler.add_collection(coll) end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/reduce.rb��������������������������������������������������0000664�0052762�0001160�00000004645�12650174557�022146� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :reduce, :type => :rvalue, :arity => -3, :doc => <<-DOC Applies a parameterized block to each element in a sequence of entries from the first argument (_the enumerable_) and returns the last result of the invocation of the parameterized block. This function takes two mandatory arguments: the first should be an Array, Hash, or something of enumerable type, and the last a parameterized block as produced by the puppet syntax: $a.reduce |$memo, $x| { ... } reduce($a) |$memo, $x| { ... } When the first argument is an Array or someting of an enumerable type, the block is called with each entry in turn. When the first argument is a hash each entry is converted to an array with `[key, value]` before being fed to the block. An optional 'start memo' value may be supplied as an argument between the array/hash and mandatory block. $a.reduce(start) |$memo, $x| { ... } reduce($a, start) |$memo, $x| { ... } If no 'start memo' is given, the first invocation of the parameterized block will be given the first and second elements of the enumeration, and if the enumerable has fewer than 2 elements, the first element is produced as the result of the reduction without invocation of the block. On each subsequent invocation, the produced value of the invoked parameterized block is given as the memo in the next invocation. Example Using reduce # Reduce an array $a = [1,2,3] $a.reduce |$memo, $entry| { $memo + $entry } #=> 6 # Reduce hash values $a = {a => 1, b => 2, c => 3} $a.reduce |$memo, $entry| { [sum, $memo[1]+$entry[1]] } #=> [sum, 6] # reverse a string "abc".reduce |$memo, $char| { "$char$memo" } #=>"cbe" It is possible to provide a starting 'memo' as an argument. Example Using reduce with given start 'memo' # Reduce an array $a = [1,2,3] $a.reduce(4) |$memo, $entry| { $memo + $entry } #=> 10 # Reduce hash values $a = {a => 1, b => 2, c => 3} $a.reduce([na, 4]) |$memo, $entry| { [sum, $memo[1]+$entry[1]] } #=> [sum, 10] Example Using reduce with an Integer range Integer[1,4].reduce |$memo, $x| { $memo + $x } #=> 10 - since 3.2 for Array and Hash - since 3.5 for additional enumerable types - note requires `parser = future`. DOC ) do |args| function_fail(["reduce() is only available when parser/evaluator future is in effect"]) end �������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/regsubst.rb������������������������������������������������0000664�0052762�0001160�00000010350�12650174557�022523� 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( :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| unless args.length.between?(3, 5) raise( ArgumentError, "regsubst(): got #{args.length} arguments, expected 3 to 5") end target, regexp, replacement, flags, lang = args reflags = 0 operation = :sub if flags == nil flags = [] elsif flags.respond_to?(:split) flags = flags.split('') else raise( Puppet::ParseError, "regsubst(): bad flags parameter #{flags.class}:`#{flags}'") end flags.each do |f| case f when 'G' then operation = :gsub when 'E' then reflags |= Regexp::EXTENDED when 'I' then reflags |= Regexp::IGNORECASE when 'M' then reflags |= Regexp::MULTILINE else raise(Puppet::ParseError, "regsubst(): bad flag `#{f}'") end end begin re = Regexp.compile(regexp, reflags, lang) rescue RegexpError, TypeError raise( Puppet::ParseError, "regsubst(): Bad regular expression `#{regexp}'") end if target.respond_to?(operation) # String parameter -> string result result = target.send(operation, re, replacement) elsif target.respond_to?(:collect) and target.respond_to?(:all?) and target.all? { |e| e.respond_to?(operation) } # Array parameter -> array result result = target.collect { |e| e.send(operation, re, replacement) } else raise( Puppet::ParseError, "regsubst(): bad target #{target.class}:`#{target}'") end return result end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/require.rb�������������������������������������������������0000664�0052762�0001160�00000004660�12650174557�022350� 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. When the future parser is used, you must use the class's full name; relative names are no longer 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. ") do |vals| # Make call patterns uniform and protected against nested arrays, also make # names absolute if so desired. vals = transform_and_assert_classnames(vals.is_a?(Array) ? vals.flatten : [vals]) # 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). # compiler.evaluate_classes(vals, self, false) vals.each do |klass| # lookup the class in the scopes if classobj = find_hostclass(klass) klass = classobj.name else raise Puppet::ParseError, "Could not find class #{klass}" end # This is a bit hackish, in some ways, but it's the only way # to configure a dependency that will make it to the client. # The 'obvious' way is just to add an edge in the catalog, # but that is considered a containment edge, not a dependency # edge, so it usually gets lost on the client. ref = Puppet::Resource.new(:class, klass) resource.set_parameter(:require, [resource[:require]].flatten.compact << ref) end end ��������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/scanf.rb���������������������������������������������������0000664�0052762�0001160�00000003012�12650174557�021754� 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 as directed by a given format string.args See the documenation 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 the format String. The result of the scan is an Array, with each sucessfully scanned and transformed value.args The scanning stops if a scan is unsuccesful and the scanned result up to that point is returned. If there was no succesful scan at all, the result is an empty Array. scanf("42", "%i")[0] == 42 When used with the future parser, an optional parameterized block may be given. The block is called with the result that is produced by scanf if no block is present, the result of the block is then returned by the function. The optional code block is typically used to assert that the scan was succesful, and either produce the same input, or perform unwrapping of the result: "42".scanf("%i") "42".scanf("%i") |$x| { unless $x[0] =~ Integer { fail "Expected a well formed integer value, got '$x[0]'" } $x[0] } - since 3.7.4 with `parser = future` - since 3.7.5 with classic parser DOC ) do |args| data = args[0] format = args[1] result = data.scanf(format) end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/search.rb��������������������������������������������������0000664�0052762�0001160�00000000775�12650174557�022144� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction(:search, :arity => -2, :doc => "Add another namespace for this class to search. This allows you to create classes with sets of definitions and add those classes to another class's search path. Deprecated in Puppet 3.7.0, to be removed in Puppet 4.0.0.") do |vals| Puppet.deprecation_warning("The 'search' function is deprecated. See http://links.puppetlabs.com/search-function-deprecation") vals.each do |val| add_namespace(val) end end ���puppet-3.8.5/lib/puppet/parser/functions/sha1.rb����������������������������������������������������0000664�0052762�0001160�00000000325�12650174557�021522� 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-3.8.5/lib/puppet/parser/functions/shellquote.rb����������������������������������������������0000664�0052762�0001160�00000004605�12650174557�023060� 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-3.8.5/lib/puppet/parser/functions/slice.rb���������������������������������������������������0000664�0052762�0001160�00000004171�12650174557�021770� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :slice, :type => :rvalue, :arity => -3, :doc => <<-DOC Applies a parameterized block to each _slice_ of elements in a sequence of selected entries from the first argument and returns the first argument, or if no block is given returns a new array with a concatenation of the slices. This function takes two mandatory arguments: the first, `$a`, should be an Array, Hash, or something of enumerable type (integer, Integer range, or String), and the second, `$n`, the number of elements to include in each slice. The optional third argument should be a a parameterized block as produced by the puppet syntax: $a.slice($n) |$x| { ... } slice($a) |$x| { ... } The parameterized block 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). In case there are fewer remaining elements than the slice size for the last slice it will contain the remaining elements. When the block has multiple parameters, excess parameters are set to undef for an array or enumerable type, and to empty arrays for a Hash. $a.slice(2) |$first, $second| { ... } When the first argument is a Hash, each `key,value` entry is counted as one, e.g, a slice size of 2 will produce an array of two arrays with key, and value. Example Using slice with Hash $a.slice(2) |$entry| { notice "first ${$entry[0]}, second ${$entry[1]}" } $a.slice(2) |$first, $second| { notice "first ${first}, second ${second}" } When called without a block, the function produces a concatenated result of the slices. Example Using slice without a block 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]] - since 3.2 for Array and Hash - since 3.5 for additional enumerable types - note requires `parser = future`. DOC ) do |args| function_fail(["slice() is only available when parser/evaluator future is in effect"]) end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/split.rb���������������������������������������������������0000664�0052762�0001160�00000001507�12650174557�022024� 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, '[.:]') `$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| return args[0].split(Regexp.compile(args[1])) end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/sprintf.rb�������������������������������������������������0000664�0052762�0001160�00000003257�12650174557�022362� 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 in Ruby for all the details." ) do |args| fmt = args.shift return sprintf(fmt, *args) end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/tag.rb�����������������������������������������������������0000664�0052762�0001160�00000000433�12650174557�021441� 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| self.resource.tag(*vals) end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/tagged.rb��������������������������������������������������0000664�0052762�0001160�00000001265�12650174557�022125� 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| configtags = compiler.catalog.tags resourcetags = resource.tags retval = true vals.each do |val| unless configtags.include?(val) or resourcetags.include?(val) retval = false break end end return retval end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/template.rb������������������������������������������������0000664�0052762�0001160�00000002372�12650174557�022505� 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| 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(':') raise Puppet::ParseError, "Failed to parse template #{file}:\n Filepath: #{info[0]}\n Line: #{info[1]}\n Detail: #{detail}\n" end end.join("") end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/functions/versioncmp.rb����������������������������������������������0000664�0052762�0001160�00000001223�12650174557�023051� 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-3.8.5/lib/puppet/parser/functions/with.rb����������������������������������������������������0000664�0052762�0001160�00000001140�12650174557�021635� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :with, :type => :rvalue, :arity => -1, :doc => <<-DOC Call a lambda code block with the given arguments. Since the parameters of the lambda are local to the lambda's scope, this can be used to create private sections of logic in a class so that the variables are not visible outside of the class. Example: # notices the array [1, 2, 'foo'] with(1, 2, 'foo') |$x, $y, $z| { notice [$x, $y, $z] } - since 3.7.0 - note requires future parser DOC ) do |args| function_fail(["with() is only available when parser/evaluator future is in effect"]) end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/grammar.ra�����������������������������������������������������������0000664�0052762�0001160�00000053750�12650174557�020315� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# vim: syntax=ruby # the parser class Puppet::Parser::Parser token STRING DQPRE DQMID DQPOST token LBRACK RBRACK LBRACE RBRACE SYMBOL FARROW COMMA TRUE token FALSE EQUALS APPENDS LESSEQUAL NOTEQUAL DOT COLON LLCOLLECT RRCOLLECT token QMARK LPAREN RPAREN ISEQUAL GREATEREQUAL GREATERTHAN LESSTHAN token IF ELSE IMPORT DEFINE ELSIF VARIABLE CLASS INHERITS NODE BOOLEAN token NAME SEMIC CASE DEFAULT AT 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 MODULO prechigh right NOT nonassoc UMINUS left IN MATCH NOMATCH left TIMES DIV MODULO left MINUS PLUS left LSHIFT RSHIFT left NOTEQUAL ISEQUAL left GREATEREQUAL GREATERTHAN LESSTHAN LESSEQUAL left AND left OR preclow rule program: statements_and_declarations | nil statements_and_declarations: statement_or_declaration { result = ast AST::BlockExpression, :children => (val[0] ? [val[0]] : []) } | statements_and_declarations statement_or_declaration { if val[1] val[0].push(val[1]) end result = val[0] } # statements is like statements_and_declarations, but it doesn't allow # nested definitions, classes, or nodes. statements: statements_and_declarations { val[0].each do |stmt| if stmt.is_a?(AST::TopLevelConstruct) error "Classes, definitions, and nodes may only appear at toplevel or inside other classes", \ :line => stmt.context[:line], :file => stmt.context[:file] end end result = val[0] } # The main list of valid statements statement_or_declaration: resource | virtualresource | collection | assignment | casestatement | ifstatement_begin | unlessstatement | import | fstatement | definition | hostclass | nodedef | resourceoverride | append | relationship keyword: AND | CASE | CLASS | DEFAULT | DEFINE | ELSE | ELSIF | IF | IN | IMPORT | INHERITS | NODE | OR | UNDEF | UNLESS relationship: relationship_side edge relationship_side { result = AST::Relationship.new(val[0], val[2], val[1][:value], ast_context) } | relationship edge relationship_side { result = AST::Relationship.new(val[0], val[2], val[1][:value], ast_context) } relationship_side: resource | resourceref | collection | variable | quotedtext | selector | casestatement | hasharrayaccesses edge: IN_EDGE | OUT_EDGE | IN_EDGE_SUB | OUT_EDGE_SUB fstatement: NAME LPAREN expressions RPAREN { result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => val[2], :ftype => :statement } | NAME LPAREN expressions COMMA RPAREN { result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => val[2], :ftype => :statement } | NAME LPAREN RPAREN { result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => AST::ASTArray.new({}), :ftype => :statement } | NAME funcvalues { result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => val[1], :ftype => :statement } funcvalues: rvalue { result = aryfy(val[0]) } # This rvalue could be an expression | funcvalues COMMA rvalue { val[0].push(val[2]) result = val[0] } expressions: expression { result = aryfy(val[0]) } | expressions comma expression { result = val[0].push(val[2]) } rvalue: quotedtext | name | type | boolean | selector | variable | array | hasharrayaccesses | resourceref | funcrvalue | undef resource: classname LBRACE resourceinstances endsemi RBRACE { @lexer.commentpop result = ast(AST::Resource, :type => val[0], :instances => val[2]) } | classname LBRACE params endcomma RBRACE { # This is a deprecated syntax. error "All resource specifications require names" } | type LBRACE params endcomma RBRACE { # a defaults setting for a type @lexer.commentpop result = ast(AST::ResourceDefaults, :type => val[0].value, :parameters => val[2]) } # Override a value set elsewhere in the configuration. resourceoverride: resourceref LBRACE anyparams endcomma RBRACE { @lexer.commentpop result = ast AST::ResourceOverride, :object => val[0], :parameters => val[2] } # Exported and virtual resources; these don't get sent to the client # unless they get collected elsewhere in the db. virtualresource: at resource { type = val[0] if (type == :exported and ! Puppet[:storeconfigs]) Puppet.warning addcontext("You cannot collect without storeconfigs being set") end error "Defaults are not virtualizable" if val[1].is_a? AST::ResourceDefaults method = type.to_s + "=" # Just mark our resource as exported and pass it through. val[1].send(method, true) result = val[1] } at: AT { result = :virtual } | AT AT { result = :exported } # A collection statement. Currently supports no arguments at all, but eventually # will, I assume. collection: type collectrhand LBRACE anyparams endcomma RBRACE { @lexer.commentpop type = val[0].value.downcase args = {:type => type} if val[1].is_a?(AST::CollExpr) args[:query] = val[1] args[:query].type = type args[:form] = args[:query].form else args[:form] = val[1] end if args[:form] == :exported and ! Puppet[:storeconfigs] Puppet.warning addcontext("You cannot collect exported resources without storeconfigs being set; the collection will be ignored") end args[:override] = val[3] result = ast AST::Collection, args } | type collectrhand { type = val[0].value.downcase args = {:type => type } if val[1].is_a?(AST::CollExpr) args[:query] = val[1] args[:query].type = type args[:form] = args[:query].form else args[:form] = val[1] end if args[:form] == :exported and ! Puppet[:storeconfigs] Puppet.warning addcontext("You cannot collect exported resources without storeconfigs being set; the collection will be ignored") end result = ast AST::Collection, args } collectrhand: LCOLLECT collstatements RCOLLECT { if val[1] result = val[1] result.form = :virtual else result = :virtual end } | LLCOLLECT collstatements RRCOLLECT { if val[1] result = val[1] result.form = :exported else result = :exported end } # A mini-language for handling collection comparisons. This is organized # to avoid the need for precedence indications. collstatements: nil | collstatement | collstatements colljoin collstatement { result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] } collstatement: collexpr | LPAREN collstatements RPAREN { result = val[1] result.parens = true } colljoin: AND { result=val[0][:value] } | OR { result=val[0][:value] } collexpr: colllval ISEQUAL expression { result = ast AST::CollExpr, :test1 => val[0], :oper => val[1][:value], :test2 => val[2] #result = ast AST::CollExpr #result.push *val } | colllval NOTEQUAL expression { result = ast AST::CollExpr, :test1 => val[0], :oper => val[1][:value], :test2 => val[2] #result = ast AST::CollExpr #result.push *val } colllval: variable | name resourceinst: resourcename COLON params endcomma { result = ast AST::ResourceInstance, :title => val[0], :parameters => val[2] } resourceinstances: resourceinst { result = aryfy(val[0]) } | resourceinstances SEMIC resourceinst { val[0].push val[2] result = val[0] } endsemi: # nothing | SEMIC undef: UNDEF { result = ast AST::Undef, :value => :undef } name: NAME { result = ast AST::Name, :value => val[0][:value], :line => val[0][:line] } type: CLASSREF { result = ast AST::Type, :value => val[0][:value], :line => val[0][:line] } resourcename: quotedtext | name | type | selector | variable | array | hasharrayaccesses assignment: VARIABLE EQUALS expression { raise Puppet::ParseError, "Cannot assign to variables in other namespaces" if val[0][:value] =~ /::/ # this is distinct from referencing a variable variable = ast AST::Name, :value => val[0][:value], :line => val[0][:line] result = ast AST::VarDef, :name => variable, :value => val[2], :line => val[0][:line] } | hasharrayaccess EQUALS expression { result = ast AST::VarDef, :name => val[0], :value => val[2] } append: VARIABLE APPENDS expression { variable = ast AST::Name, :value => val[0][:value], :line => val[0][:line] result = ast AST::VarDef, :name => variable, :value => val[2], :append => true, :line => val[0][:line] } params: # nothing { result = ast AST::ASTArray } | param { result = aryfy(val[0]) } | params COMMA param { val[0].push(val[2]) result = val[0] } param_name: NAME | keyword | BOOLEAN param: param_name FARROW expression { result = ast AST::ResourceParam, :param => val[0][:value], :line => val[0][:line], :value => val[2] } addparam: NAME PARROW expression { result = ast AST::ResourceParam, :param => val[0][:value], :line => val[0][:line], :value => val[2], :add => true } anyparam: param | addparam anyparams: # nothing { result = ast AST::ASTArray } | anyparam { result = aryfy(val[0]) } | anyparams COMMA anyparam { val[0].push(val[2]) result = val[0] } # We currently require arguments in these functions. funcrvalue: NAME LPAREN expressions RPAREN { result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => val[2], :ftype => :rvalue } | NAME LPAREN RPAREN { result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => AST::ASTArray.new({}), :ftype => :rvalue } quotedtext: STRING { result = ast AST::String, :value => val[0][:value], :line => val[0][:line] } | DQPRE dqrval { result = ast AST::Concat, :value => [ast(AST::String,val[0])]+val[1], :line => val[0][:line] } dqrval: expression dqtail { result = [val[0]] + val[1] } dqtail: DQPOST { result = [ast(AST::String,val[0])] } | DQMID dqrval { result = [ast(AST::String,val[0])] + val[1] } boolean: BOOLEAN { result = ast AST::Boolean, :value => val[0][:value], :line => val[0][:line] } resourceref: NAME LBRACK expressions RBRACK { Puppet.warning addcontext("Deprecation notice: Resource references should now be capitalized") result = ast AST::ResourceReference, :type => val[0][:value], :line => val[0][:line], :title => val[2] } | type LBRACK expressions RBRACK { result = ast AST::ResourceReference, :type => val[0].value, :title => val[2] } unlessstatement: UNLESS expression LBRACE statements RBRACE { @lexer.commentpop args = { :test => ast(AST::Not, :value => val[1]), :statements => val[3] } result = ast AST::IfStatement, args } | UNLESS expression LBRACE RBRACE { @lexer.commentpop args = { :test => ast(AST::Not, :value => val[1]), :statements => ast(AST::Nop) } result = ast AST::IfStatement, args } ifstatement_begin: IF ifstatement { result = val[1] } ifstatement: expression LBRACE statements RBRACE else { @lexer.commentpop args = { :test => val[0], :statements => val[2] } args[:else] = val[4] if val[4] result = ast AST::IfStatement, args } | expression LBRACE RBRACE else { @lexer.commentpop args = { :test => val[0], :statements => ast(AST::Nop) } args[:else] = val[3] if val[3] result = ast AST::IfStatement, args } else: # nothing | ELSIF ifstatement { result = ast AST::Else, :statements => val[1] } | ELSE LBRACE statements RBRACE { @lexer.commentpop result = ast AST::Else, :statements => val[2] } | ELSE LBRACE RBRACE { @lexer.commentpop result = ast AST::Else, :statements => ast(AST::Nop) } # Unlike yacc/bison, it seems racc # gives tons of shift/reduce warnings # with the following syntax: # # expression: ... # | expression arithop expressio { ... } # # arithop: PLUS | MINUS | DIVIDE | TIMES ... # # So I had to develop the expression by adding one rule # per operator :-( expression: rvalue | hash | expression IN expression { result = ast AST::InOperator, :lval => val[0], :rval => val[2] } | expression MATCH regex { result = ast AST::MatchOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression NOMATCH regex { result = ast AST::MatchOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression PLUS expression { result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression MINUS expression { result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression DIV expression { result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression TIMES expression { result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression MODULO expression { result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression LSHIFT expression { result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression RSHIFT expression { result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | MINUS expression =UMINUS { result = ast AST::Minus, :value => val[1] } | expression NOTEQUAL expression { result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression ISEQUAL expression { result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression GREATERTHAN expression { result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression GREATEREQUAL expression { result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression LESSTHAN expression { result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression LESSEQUAL expression { result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | NOT expression { result = ast AST::Not, :value => val[1] } | expression AND expression { result = ast AST::BooleanOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | expression OR expression { result = ast AST::BooleanOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] } | LPAREN expression RPAREN { result = val[1] } casestatement: CASE expression LBRACE caseopts RBRACE { @lexer.commentpop result = ast AST::CaseStatement, :test => val[1], :options => val[3] } caseopts: caseopt { result = aryfy(val[0]) } | caseopts caseopt { val[0].push val[1] result = val[0] } caseopt: casevalues COLON LBRACE statements RBRACE { @lexer.commentpop result = ast AST::CaseOpt, :value => val[0], :statements => val[3] } | casevalues COLON LBRACE RBRACE { @lexer.commentpop result = ast( AST::CaseOpt, :value => val[0], :statements => ast(AST::BlockExpression) ) } casevalues: selectlhand { result = aryfy(val[0]) } | casevalues COMMA selectlhand { val[0].push(val[2]) result = val[0] } selector: selectlhand QMARK svalues { result = ast AST::Selector, :param => val[0], :values => val[2] } svalues: selectval | LBRACE sintvalues endcomma RBRACE { @lexer.commentpop result = val[1] } sintvalues: selectval | sintvalues comma selectval { if val[0].instance_of?(AST::ASTArray) val[0].push(val[2]) result = val[0] else result = ast AST::ASTArray, :children => [val[0],val[2]] end } selectval: selectlhand FARROW rvalue { result = ast AST::ResourceParam, :param => val[0], :value => val[2] } selectlhand: name | type | quotedtext | variable | funcrvalue | boolean | undef | hasharrayaccess | DEFAULT { result = ast AST::Default, :value => val[0][:value], :line => val[0][:line] } | regex # These are only used for importing, and we don't interpolate there. string: STRING { result = [val[0][:value]] } strings: string | strings COMMA string { result = val[0] += val[2] } import: IMPORT strings { val[1].each do |file| import(file) end result = nil } # Disable definition inheritance for now. 8/27/06, luke #definition: DEFINE NAME argumentlist parent LBRACE statements RBRACE { definition: DEFINE classname argumentlist LBRACE statements RBRACE { @lexer.commentpop result = Puppet::Parser::AST::Definition.new(classname(val[1]), ast_context(true).merge(:arguments => val[2], :code => val[4], :line => val[0][:line])) @lexer.indefine = false #} | DEFINE NAME argumentlist parent LBRACE RBRACE { } | DEFINE classname argumentlist LBRACE RBRACE { @lexer.commentpop result = Puppet::Parser::AST::Definition.new(classname(val[1]), ast_context(true).merge(:arguments => val[2], :line => val[0][:line])) @lexer.indefine = false } #hostclass: CLASS NAME argumentlist parent LBRACE statements RBRACE { hostclass: CLASS classname argumentlist classparent LBRACE statements_and_declarations RBRACE { @lexer.commentpop # Our class gets defined in the parent namespace, not our own. @lexer.namepop result = Puppet::Parser::AST::Hostclass.new(classname(val[1]), ast_context(true).merge(:arguments => val[2], :parent => val[3], :code => val[5], :line => val[0][:line])) } | CLASS classname argumentlist classparent LBRACE RBRACE { @lexer.commentpop # Our class gets defined in the parent namespace, not our own. @lexer.namepop result = Puppet::Parser::AST::Hostclass.new(classname(val[1]), ast_context(true).merge(:arguments => val[2], :parent => val[3], :line => val[0][:line])) } nodedef: NODE hostnames nodeparent LBRACE statements RBRACE { @lexer.commentpop result = Puppet::Parser::AST::Node.new(val[1], ast_context(true).merge(:parent => val[2], :code => val[4], :line => val[0][:line])) } | NODE hostnames nodeparent LBRACE RBRACE { @lexer.commentpop result = Puppet::Parser::AST::Node.new(val[1], ast_context(true).merge(:parent => val[2], :line => val[0][:line])) } classname: NAME { result = val[0][:value] } | CLASS { result = "class" } # Multiple hostnames, as used for node names. These are all literal # strings, not AST objects. hostnames: nodename { result = [result] } | hostnames COMMA nodename { result = val[0] result << val[2] } nodename: hostname { result = ast AST::HostName, :value => val[0] } hostname: NAME { result = val[0][:value] } | STRING { result = val[0][:value] } | DEFAULT { result = val[0][:value] } | regex nil: { result = nil } nothing: { result = ast AST::ASTArray, :children => [] } argumentlist: nil | LPAREN nothing RPAREN { result = nil } | LPAREN arguments endcomma RPAREN { result = val[1] result = [result] unless result[0].is_a?(Array) } arguments: argument | arguments COMMA argument { result = val[0] result = [result] unless result[0].is_a?(Array) result << val[2] } argument: VARIABLE EQUALS expression { result = [val[0][:value], val[2]] } | VARIABLE { result = [val[0][:value]] } nodeparent: nil | INHERITS hostname { result = val[1] } classparent: nil | INHERITS classnameordefault { result = val[1] } classnameordefault: classname | DEFAULT variable: VARIABLE { result = ast AST::Variable, :value => val[0][:value], :line => val[0][:line] } array: LBRACK expressions RBRACK { result = val[1] } | LBRACK expressions COMMA RBRACK { result = val[1] } | LBRACK RBRACK { result = ast AST::ASTArray } comma: FARROW | COMMA endcomma: # nothing | COMMA { result = nil } regex: REGEX { result = ast AST::Regex, :value => val[0][:value] } hash: LBRACE hashpairs RBRACE { @lexer.commentpop if val[1].instance_of?(AST::ASTHash) result = val[1] else result = ast AST::ASTHash, { :value => val[1] } end } | LBRACE hashpairs COMMA RBRACE { @lexer.commentpop if val[1].instance_of?(AST::ASTHash) result = val[1] else result = ast AST::ASTHash, { :value => val[1] } end } | LBRACE RBRACE { @lexer.commentpop result = ast AST::ASTHash } hashpairs: hashpair | hashpairs COMMA hashpair { if val[0].instance_of?(AST::ASTHash) result = val[0].merge(val[2]) else result = ast AST::ASTHash, :value => val[0] result.merge(val[2]) end } hashpair: key FARROW expression { result = ast AST::ASTHash, { :value => { val[0] => val[2] } } } key: NAME { result = val[0][:value] } | quotedtext { result = val[0] } hasharrayaccess: VARIABLE LBRACK expression RBRACK { result = ast AST::HashOrArrayAccess, :variable => val[0][:value], :key => val[2] } hasharrayaccesses: hasharrayaccess | hasharrayaccesses LBRACK expression RBRACK { result = ast AST::HashOrArrayAccess, :variable => val[0], :key => val[2] } end ---- header ---- require 'puppet' require 'puppet/parser/lexer' require 'puppet/parser/ast' module Puppet class ParseError < Puppet::Error; end class ImportError < Racc::ParseError; end class AlreadyImportedError < ImportError; end end ---- inner ---- # It got too annoying having code in a file that needs to be compiled. require 'puppet/parser/parser_support' # Make emacs happy # Local Variables: # mode: ruby # End: ������������������������puppet-3.8.5/lib/puppet/parser/lexer.rb�������������������������������������������������������������0000664�0052762�0001160�00000041174�12650174557�020004� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# the scanner/lexer require 'forwardable' require 'strscan' require 'puppet' require 'puppet/util/methodhelper' module Puppet class LexError < RuntimeError; end end module Puppet::Parser; end class Puppet::Parser::Lexer extend Forwardable attr_reader :last, :file, :lexing_context, :token_queue attr_accessor :line, :indefine alias :indefine? :indefine # Returns the position on the line. # This implementation always returns nil. It is here for API reasons in Puppet::Error # which needs to support both --parser current, and --parser future. # def pos # Make the lexer comply with newer API. It does not produce a pos... nil end def lex_error msg raise Puppet::LexError.new(msg) end class Token ALWAYS_ACCEPTABLE = Proc.new { |context| true } include Puppet::Util::MethodHelper attr_accessor :regex, :name, :string, :skip, :incr_line, :skip_text, :accumulate alias skip? skip alias accumulate? accumulate def initialize(string_or_regex, name, options = {}) if string_or_regex.is_a?(String) @name, @string = name, string_or_regex @regex = Regexp.new(Regexp.escape(string_or_regex)) else @name, @regex = name, string_or_regex end set_options(options) @acceptable_when = ALWAYS_ACCEPTABLE end def to_s string or @name.to_s end def acceptable?(context={}) @acceptable_when.call(context) end # Define when the token is able to match. # This provides context that cannot be expressed otherwise, such as feature flags. # # @param block [Proc] a proc that given a context returns a boolean def acceptable_when(block) @acceptable_when = block end end # Maintain a list of tokens. class TokenList extend Forwardable attr_reader :regex_tokens, :string_tokens def_delegator :@tokens, :[] # Create a new token. def add_token(name, regex, options = {}, &block) raise(ArgumentError, "Token #{name} already exists") if @tokens.include?(name) token = Token.new(regex, name, options) @tokens[token.name] = token if token.string @string_tokens << token @tokens_by_string[token.string] = token else @regex_tokens << token end token.meta_def(:convert, &block) if block_given? token end def initialize @tokens = {} @regex_tokens = [] @string_tokens = [] @tokens_by_string = {} end # Look up a token by its value, rather than name. def lookup(string) @tokens_by_string[string] end # Define more tokens. def add_tokens(hash) hash.each do |regex, name| add_token(name, regex) end end # Sort our tokens by length, so we know once we match, we're done. # This helps us avoid the O(n^2) nature of token matching. def sort_tokens @string_tokens.sort! { |a, b| b.string.length <=> a.string.length } end # Yield each token name and value in turn. def each @tokens.each {|name, value| yield name, value } end end TOKENS = TokenList.new TOKENS.add_tokens( '[' => :LBRACK, ']' => :RBRACK, '{' => :LBRACE, '}' => :RBRACE, '(' => :LPAREN, ')' => :RPAREN, '=' => :EQUALS, '+=' => :APPENDS, '==' => :ISEQUAL, '>=' => :GREATEREQUAL, '>' => :GREATERTHAN, '<' => :LESSTHAN, '<=' => :LESSEQUAL, '!=' => :NOTEQUAL, '!' => :NOT, ',' => :COMMA, '.' => :DOT, ':' => :COLON, '@' => :AT, '<<|' => :LLCOLLECT, '|>>' => :RRCOLLECT, '->' => :IN_EDGE, '<-' => :OUT_EDGE, '~>' => :IN_EDGE_SUB, '<~' => :OUT_EDGE_SUB, '<|' => :LCOLLECT, '|>' => :RCOLLECT, ';' => :SEMIC, '?' => :QMARK, '\\' => :BACKSLASH, '=>' => :FARROW, '+>' => :PARROW, '+' => :PLUS, '-' => :MINUS, '/' => :DIV, '*' => :TIMES, '%' => :MODULO, '<<' => :LSHIFT, '>>' => :RSHIFT, '=~' => :MATCH, '!~' => :NOMATCH, %r{((::){0,1}[A-Z][-\w]*)+} => :CLASSREF, "<string>" => :STRING, "<dqstring up to first interpolation>" => :DQPRE, "<dqstring between two interpolations>" => :DQMID, "<dqstring after final interpolation>" => :DQPOST, "<boolean>" => :BOOLEAN ) module Contextual QUOTE_TOKENS = [:DQPRE,:DQMID] REGEX_INTRODUCING_TOKENS = [:NODE,:LBRACE,:RBRACE,:MATCH,:NOMATCH,:COMMA] NOT_INSIDE_QUOTES = Proc.new do |context| !QUOTE_TOKENS.include? context[:after] end INSIDE_QUOTES = Proc.new do |context| QUOTE_TOKENS.include? context[:after] end IN_REGEX_POSITION = Proc.new do |context| REGEX_INTRODUCING_TOKENS.include? context[:after] end IN_STRING_INTERPOLATION = Proc.new do |context| context[:string_interpolation_depth] > 0 end DASHED_VARIABLES_ALLOWED = Proc.new do |context| Puppet[:allow_variables_with_dashes] end VARIABLE_AND_DASHES_ALLOWED = Proc.new do |context| Contextual::DASHED_VARIABLES_ALLOWED.call(context) and TOKENS[:VARIABLE].acceptable?(context) end end # Numbers are treated separately from names, so that they may contain dots. TOKENS.add_token :NUMBER, %r{\b(?:0[xX][0-9A-Fa-f]+|0?\d+(?:\.\d+)?(?:[eE]-?\d+)?)\b} do |lexer, value| [TOKENS[:NAME], value] end TOKENS[:NUMBER].acceptable_when Contextual::NOT_INSIDE_QUOTES TOKENS.add_token :NAME, %r{((::)?[a-z0-9][-\w]*)(::[a-z0-9][-\w]*)*} do |lexer, value| string_token = self # we're looking for keywords here if tmp = KEYWORDS.lookup(value) string_token = tmp if [:TRUE, :FALSE].include?(string_token.name) value = eval(value) string_token = TOKENS[:BOOLEAN] end end [string_token, value] end [:NAME, :CLASSREF].each do |name_token| TOKENS[name_token].acceptable_when Contextual::NOT_INSIDE_QUOTES end TOKENS.add_token :COMMENT, %r{#.*}, :accumulate => true, :skip => true do |lexer,value| value.sub!(/# ?/,'') [self, value] end TOKENS.add_token :MLCOMMENT, %r{/\*(.*?)\*/}m, :accumulate => true, :skip => true do |lexer, value| lexer.line += value.count("\n") value.sub!(/^\/\* ?/,'') value.sub!(/ ?\*\/$/,'') [self,value] end TOKENS.add_token :REGEX, %r{/[^/\n]*/} do |lexer, value| # Make sure we haven't matched an escaped / while value[-2..-2] == '\\' other = lexer.scan_until(%r{/}) value += other end regex = value.sub(%r{\A/}, "").sub(%r{/\Z}, '').gsub("\\/", "/") [self, Regexp.new(regex)] end TOKENS[:REGEX].acceptable_when Contextual::IN_REGEX_POSITION TOKENS.add_token :RETURN, "\n", :skip => true, :incr_line => true, :skip_text => true TOKENS.add_token :SQUOTE, "'" do |lexer, value| [TOKENS[:STRING], lexer.slurpstring(value,["'"],:ignore_invalid_escapes).first ] end DQ_initial_token_types = {'$' => :DQPRE,'"' => :STRING} DQ_continuation_token_types = {'$' => :DQMID,'"' => :DQPOST} TOKENS.add_token :DQUOTE, /"/ do |lexer, value| lexer.tokenize_interpolated_string(DQ_initial_token_types) end TOKENS.add_token :DQCONT, /\}/ do |lexer, value| lexer.tokenize_interpolated_string(DQ_continuation_token_types) end TOKENS[:DQCONT].acceptable_when Contextual::IN_STRING_INTERPOLATION TOKENS.add_token :DOLLAR_VAR_WITH_DASH, %r{\$(?:::)?(?:[-\w]+::)*[-\w]+} do |lexer, value| lexer.warn_if_variable_has_hyphen(value) [TOKENS[:VARIABLE], value[1..-1]] end TOKENS[:DOLLAR_VAR_WITH_DASH].acceptable_when Contextual::DASHED_VARIABLES_ALLOWED TOKENS.add_token :DOLLAR_VAR, %r{\$(::)?(\w+::)*\w+} do |lexer, value| [TOKENS[:VARIABLE],value[1..-1]] end TOKENS.add_token :VARIABLE_WITH_DASH, %r{(?:::)?(?:[-\w]+::)*[-\w]+} do |lexer, value| lexer.warn_if_variable_has_hyphen(value) [TOKENS[:VARIABLE], value] end TOKENS[:VARIABLE_WITH_DASH].acceptable_when Contextual::VARIABLE_AND_DASHES_ALLOWED TOKENS.add_token :VARIABLE, %r{(::)?(\w+::)*\w+} TOKENS[:VARIABLE].acceptable_when Contextual::INSIDE_QUOTES TOKENS.sort_tokens @@pairs = { "{" => "}", "(" => ")", "[" => "]", "<|" => "|>", "<<|" => "|>>" } KEYWORDS = TokenList.new KEYWORDS.add_tokens( "case" => :CASE, "class" => :CLASS, "default" => :DEFAULT, "define" => :DEFINE, "import" => :IMPORT, "if" => :IF, "elsif" => :ELSIF, "else" => :ELSE, "inherits" => :INHERITS, "node" => :NODE, "and" => :AND, "or" => :OR, "undef" => :UNDEF, "false" => :FALSE, "true" => :TRUE, "in" => :IN, "unless" => :UNLESS ) def clear initvars end def expected return nil if @expected.empty? name = @expected[-1] TOKENS.lookup(name) or lex_error "Could not find expected token #{name}" end # scan the whole file # basically just used for testing def fullscan array = [] self.scan { |token, str| # Ignore any definition nesting problems @indefine = false array.push([token,str]) } array end def file=(file) @file = file @line = 1 contents = Puppet::FileSystem.exist?(file) ? Puppet::FileSystem.read(file) : "" @scanner = StringScanner.new(contents) end def_delegator :@token_queue, :shift, :shift_token def find_string_token # We know our longest string token is three chars, so try each size in turn # until we either match or run out of chars. This way our worst-case is three # tries, where it is otherwise the number of string token we have. Also, # the lookups are optimized hash lookups, instead of regex scans. # s = @scanner.peek(3) token = TOKENS.lookup(s[0,3]) || TOKENS.lookup(s[0,2]) || TOKENS.lookup(s[0,1]) [ token, token && @scanner.scan(token.regex) ] end # Find the next token that matches a regex. We look for these first. def find_regex_token best_token = nil best_length = 0 # I tried optimizing based on the first char, but it had # a slightly negative affect and was a good bit more complicated. TOKENS.regex_tokens.each do |token| if length = @scanner.match?(token.regex) and token.acceptable?(lexing_context) # We've found a longer match if length > best_length best_length = length best_token = token end end end return best_token, @scanner.scan(best_token.regex) if best_token end # Find the next token, returning the string and the token. def find_token shift_token || find_regex_token || find_string_token end def initialize initvars end def initvars @line = 1 @previous_token = nil @scanner = nil @file = nil # AAARRGGGG! okay, regexes in ruby are bloody annoying # no one else has "\n" =~ /\s/ @skip = %r{[ \t\r]+} @namestack = [] @token_queue = [] @indefine = false @expected = [] @commentstack = [ ['', @line] ] @lexing_context = { :after => nil, :start_of_line => true, :string_interpolation_depth => 0 } end # Make any necessary changes to the token and/or value. def munge_token(token, value) @line += 1 if token.incr_line skip if token.skip_text return if token.skip and not token.accumulate? token, value = token.convert(self, value) if token.respond_to?(:convert) return unless token if token.accumulate? comment = @commentstack.pop comment[0] << value + "\n" @commentstack.push(comment) end return if token.skip return token, { :value => value, :line => @line } end # Handling the namespace stack def_delegator :@namestack, :pop, :namepop # This value might have :: in it, but we don't care -- it'll be handled # normally when joining, and when popping we want to pop this full value, # however long the namespace is. def_delegator :@namestack, :<<, :namestack # Collect the current namespace. def namespace @namestack.join("::") end def_delegator :@scanner, :rest # this is the heart of the lexer def scan #Puppet.debug("entering scan") lex_error "Invalid or empty string" unless @scanner # Skip any initial whitespace. skip until token_queue.empty? and @scanner.eos? do matched_token, value = find_token # error out if we didn't match anything at all lex_error "Could not match #{@scanner.rest[/^(\S+|\s+|.*)/]}" unless matched_token newline = matched_token.name == :RETURN # this matches a blank line; eat the previously accumulated comments getcomment if lexing_context[:start_of_line] and newline lexing_context[:start_of_line] = newline final_token, token_value = munge_token(matched_token, value) unless final_token skip next end final_token_name = final_token.name lexing_context[:after] = final_token_name unless newline lexing_context[:string_interpolation_depth] += 1 if final_token_name == :DQPRE lexing_context[:string_interpolation_depth] -= 1 if final_token_name == :DQPOST value = token_value[:value] if match = @@pairs[value] and final_token_name != :DQUOTE and final_token_name != :SQUOTE @expected << match elsif exp = @expected[-1] and exp == value and final_token_name != :DQUOTE and final_token_name != :SQUOTE @expected.pop end if final_token_name == :LBRACE or final_token_name == :LPAREN commentpush end if final_token_name == :RPAREN commentpop end yield [final_token_name, token_value] if @previous_token namestack(value) if @previous_token.name == :CLASS and value != '{' if @previous_token.name == :DEFINE if indefine? msg = "Cannot nest definition #{value} inside #{@indefine}" self.indefine = false raise Puppet::ParseError, msg end @indefine = value end end @previous_token = final_token skip end @scanner = nil # This indicates that we're done parsing. yield [false,false] end # Skip any skipchars in our remaining string. def skip @scanner.skip(@skip) end # Provide some limited access to the scanner, for those # tokens that need it. def_delegator :@scanner, :scan_until # we've encountered the start of a string... # slurp in the rest of the string and return it def slurpstring(terminators,escapes=%w{ \\ $ ' " r n t s }+["\n"],ignore_invalid_escapes=false) # we search for the next quote that isn't preceded by a # backslash; the caret is there to match empty strings str = @scanner.scan_until(/([^\\]|^|[^\\])([\\]{2})*[#{terminators}]/) or lex_error "Unclosed quote after '#{last}' in '#{rest}'" @line += str.count("\n") # literal carriage returns add to the line count. str.gsub!(/\\(.)/m) { ch = $1 if escapes.include? ch case ch when 'r'; "\r" when 'n'; "\n" when 't'; "\t" when 's'; " " when "\n"; '' else ch end else Puppet.warning "Unrecognised escape sequence '\\#{ch}'#{file && " in file #{file}"}#{line && " at line #{line}"}" unless ignore_invalid_escapes "\\#{ch}" end } [ str[0..-2],str[-1,1] ] end def tokenize_interpolated_string(token_type,preamble='') value,terminator = slurpstring('"$') token_queue << [TOKENS[token_type[terminator]],preamble+value] variable_regex = if Puppet[:allow_variables_with_dashes] TOKENS[:VARIABLE_WITH_DASH].regex else TOKENS[:VARIABLE].regex end if terminator != '$' or @scanner.scan(/\{/) token_queue.shift elsif var_name = @scanner.scan(variable_regex) warn_if_variable_has_hyphen(var_name) token_queue << [TOKENS[:VARIABLE],var_name] tokenize_interpolated_string(DQ_continuation_token_types) else tokenize_interpolated_string(token_type,token_queue.pop.last + terminator) end end # just parse a string, not a whole file def string=(string) @scanner = StringScanner.new(string) end # returns the content of the currently accumulated content cache def commentpop @commentstack.pop[0] end def getcomment(line = nil) comment = @commentstack.last if line.nil? or comment[1] <= line @commentstack.pop @commentstack.push(['', @line]) return comment[0] end '' end def commentpush @commentstack.push(['', @line]) end def warn_if_variable_has_hyphen(var_name) if var_name.include?('-') Puppet.deprecation_warning("Using `-` in variable names is deprecated at #{file || '<string>'}:#{line}. See http://links.puppetlabs.com/puppet-hyphenated-variable-deprecation") end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/makefile�������������������������������������������������������������0000664�0052762�0001160�00000000231�12650174557�020025� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#parser.rb: grammar.ry # ryacc --output parser grammar parser.rb: grammar.ra racc -o$@ grammar.ra grammar.output: grammar.ra racc -v -o$@ grammar.ra �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/methods.rb�����������������������������������������������������������0000664�0052762�0001160�00000004057�12650174557�020327� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/autoload' require 'puppet/parser/scope' require 'puppet/parser/functions' require 'monitor' # A module for handling finding and invoking methods (functions invokable as a method). # A method call on the form: # # $a.meth(1,2,3] {|...| ...} # # will lookup a function called 'meth' and call it with the arguments ($a, 1, 2, 3, <lambda>) # # @see Puppet::Parser::AST::Lambda # @see Puppet::Parser::AST::MethodCall # # @api public # @since 3.2 # module Puppet::Parser::Methods Environment = Puppet::Node::Environment # Represents an invokable method configured to be invoked for a given object. # class Method def initialize(receiver, obj, method_name, rvalue) @receiver = receiver @o = obj @method_name = method_name @rvalue = rvalue end # Invoke this method's function in the given scope with the given arguments and parameterized block. # A method call on the form: # # $a.meth(1,2,3) {|...| ...} # # results in the equivalent: # # meth($a, 1, 2, 3, {|...| ... }) # # @param scope [Puppet::Parser::Scope] the scope the call takes place in # @param args [Array<Object>] arguments 1..n to pass to the function # @param pblock [Puppet::Parser::AST::Lambda] optional parameterized block to pass as the last argument # to the called function # def invoke(scope, args=[], pblock=nil) arguments = [@o] + args arguments << pblock if pblock @receiver.send(@method_name, arguments) end # @return [Boolean] whether the method function produces an rvalue or not. def is_rvalue? @rvalue end end class << self include Puppet::Util end # Finds a function and returns an instance of Method configured to perform invocation. # @return [Method, nil] configured method or nil if method not found def self.find_method(scope, receiver, name) fname = Puppet::Parser::Functions.function(name) rvalue = Puppet::Parser::Functions.rvalue?(name) return Method.new(scope, receiver, fname, rvalue) if fname nil end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/parser.rb������������������������������������������������������������0000664�0052762�0001160�00000226553�12650174557�020167� 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/parser/lexer' require 'puppet/parser/ast' module Puppet class ParseError < Puppet::Error; end class ImportError < Racc::ParseError; end class AlreadyImportedError < ImportError; end end module Puppet module Parser class Parser < Racc::Parser module_eval(<<'...end grammar.ra/module_eval...', 'grammar.ra', 799) # It got too annoying having code in a file that needs to be compiled. require 'puppet/parser/parser_support' # Make emacs happy # Local Variables: # mode: ruby # End: ...end grammar.ra/module_eval... ##### State transition tables begin ### clist = [ '35,36,199,198,246,159,-130,86,-112,82,277,357,361,379,356,215,210,159', '276,-197,360,158,85,158,211,213,212,214,39,248,48,49,267,33,50,158,51', '37,26,-129,40,46,30,35,36,32,84,217,216,31,398,203,204,206,205,208,209', '-122,201,202,52,-130,-130,-130,-130,200,38,207,35,36,278,39,86,48,49', '350,33,50,90,51,37,26,89,40,46,30,35,36,32,-178,94,253,31,257,280,257', '364,274,273,92,93,215,210,52,257,255,226,336,62,38,211,213,212,214,39', '338,48,49,254,33,50,339,51,37,26,347,40,46,30,35,36,32,-180,217,216', '31,310,203,204,206,205,208,209,341,201,202,52,35,36,274,273,200,38,207', '223,303,-178,39,304,48,49,-177,33,50,185,51,37,26,95,40,46,30,35,36', '32,190,-184,281,31,397,189,344,90,201,202,226,89,215,210,52,200,357', '250,32,356,38,211,213,212,214,39,-179,48,49,268,33,50,90,51,37,26,89', '40,46,30,35,36,32,185,217,216,31,395,203,204,206,205,208,209,190,201', '202,52,119,189,201,202,200,38,207,201,202,200,39,119,48,49,200,33,50', '185,51,37,26,267,40,46,30,35,36,32,190,185,90,31,392,189,89,119,265', '375,118,337,190,120,52,257,280,189,302,-96,38,118,257,263,120,39,-185', '48,49,52,33,50,52,51,37,26,-123,40,46,30,35,36,32,52,103,118,31,252', '120,279,206,205,251,257,280,201,202,52,250,243,243,262,200,38,207,257', '263,52,137,135,139,134,136,80,132,140,141,177,168,240,131,162,35,36', '82,32,180,142,130,163,271,94,-184,274,273,203,204,206,205,-183,52,-181', '201,202,62,138,144,-180,353,200,39,207,48,49,354,33,50,330,51,37,26', '-182,40,46,30,35,36,32,-177,52,-179,31,367,203,204,206,205,157,368,370', '201,202,52,35,36,371,372,200,38,207,122,327,326,39,110,48,49,109,33', '50,267,51,37,26,334,40,46,30,35,36,32,91,81,62,31,377,80,90,323,-179', '37,128,382,40,46,52,35,36,32,383,-180,38,31,385,61,-228,39,387,48,49', '388,33,50,52,51,37,26,323,40,46,30,35,36,32,319,110,393,31,308,80,90', '317,305,37,128,158,40,46,52,53,399,32,400,,38,31,,,,39,,48,49,,33,50', '52,51,37,26,,40,46,30,230,,32,,,,31,,,215,210,56,57,58,59,,,52,211,213', '212,214,,38,203,204,206,205,208,209,,201,202,56,57,58,59,,200,,207,217', '216,210,,203,204,206,205,208,209,211,201,202,228,-38,-38,-38,-38,200', ',207,,215,210,-44,-44,-44,-44,,,,211,213,212,214,,,203,204,206,205,208', '209,,201,202,-40,-40,-40,-40,,200,,207,217,216,,,203,204,206,205,208', '209,,201,202,229,,,,,200,,207,,215,210,206,205,,,,201,202,211,213,212', '214,,200,,207,,,,35,36,,,103,,104,,,,,,217,216,,,203,204,206,205,208', '209,102,201,202,35,36,,,103,200,104,207,80,,,,37,77,,,46,,,,32,101,102', ',31,35,36,100,,103,,104,,80,,52,,37,77,,,46,,,,32,101,102,,31,35,36', '100,,103,,104,,80,,52,,37,77,,,46,,,,32,101,102,,31,35,36,100,,103,', '104,,80,,52,,37,77,,,46,,,,32,101,102,,31,35,36,100,,103,,104,,80,,52', ',37,77,,,46,,,,32,101,102,,31,35,36,100,,103,,104,,80,,52,,37,77,,,46', ',,,32,101,102,,31,35,36,100,,103,,104,,80,,52,,37,77,,,46,,,,32,101', '102,,31,35,36,100,,103,,104,,80,,52,,37,77,,,46,,,,32,101,102,,31,35', '36,100,,103,,104,,80,,52,,37,77,,,46,,,,32,101,102,,31,35,36,100,,103', ',104,,80,,52,,37,77,,,46,,,,32,101,102,,31,35,36,100,,103,,104,,80,', '52,,37,77,,,46,,,,32,101,102,,31,35,36,100,,103,,104,,80,,52,,37,77', ',,46,35,36,,32,101,102,155,31,,,100,,,35,36,,80,103,52,,37,77,,,46,', ',,32,101,35,36,31,80,103,100,104,37,231,,,46,,52,,32,80,,,31,37,77,102', ',46,35,36,,32,103,52,104,31,80,,35,36,37,77,,,46,358,52,,32,101,102', ',31,,,100,35,36,,,,80,,52,,37,77,,,46,,80,,32,101,37,231,31,,46,100', ',,32,,,,31,52,80,,,,37,231,,,46,52,,,32,35,36,,31,103,,104,,,,,,,,52', ',,35,36,,,103,102,104,,,,,,,,,,80,,,,37,77,102,,46,,,,32,101,,,31,80', ',100,,37,77,,,46,,52,,32,101,35,36,31,,103,100,104,,,,35,36,,52,103', ',104,,,,35,36,102,,103,161,104,,,,,,102,80,,,,37,77,,,46,102,80,,32', '101,37,77,31,,46,100,80,,32,101,37,77,31,52,46,100,,,32,101,35,36,31', '52,103,100,104,,,,35,36,,52,103,,104,,,,35,36,102,,103,,104,,,,,,102', '80,,,,37,77,,,46,102,80,,32,101,37,77,31,,46,100,80,,32,101,37,77,31', '52,46,100,,,32,101,35,36,31,52,103,100,104,,,,35,36,,52,78,,-197,,,', '35,36,102,,103,,104,,,,,,63,80,,,,37,77,,,46,102,80,,32,101,37,77,31', ',46,100,80,,32,,37,77,31,52,46,,,,32,101,35,36,31,52,103,100,104,,,', '35,36,,52,103,,104,,,,35,36,102,,103,,104,,,,,,102,80,,,,37,77,,,46', '102,80,,32,101,37,77,31,,46,100,80,,32,101,37,77,31,52,46,100,,,32,101', '35,36,31,52,103,100,104,,,,35,36,,52,103,,104,,,,35,36,102,,103,,104', ',,,,,102,80,,,,37,77,,,46,102,80,,32,101,37,77,31,,46,100,80,,32,101', '37,77,31,52,46,100,,,32,101,35,36,31,52,103,100,104,,,,35,36,,52,103', ',104,,,,35,36,102,,103,,104,,,,,,102,80,,,,37,77,,,46,102,80,,32,101', '37,77,31,,46,100,80,,32,101,37,77,31,52,46,100,,,32,101,35,36,31,52', '103,100,104,,,,35,36,,52,103,161,104,,,,35,36,102,,103,,104,,,,,,102', '80,,,,37,77,,,46,102,80,,32,101,37,77,31,,46,100,80,,32,101,37,77,31', '52,46,100,,,32,101,35,36,31,52,103,100,104,,,,35,36,,52,,,,,,,35,36', '102,,,,234,,,,35,36,,80,103,,,37,77,,,46,,80,,32,101,37,231,31,,46,100', '80,,32,,37,231,31,52,46,,80,,32,,37,231,31,52,46,35,36,,32,103,,104', '31,52,,35,36,,,103,,104,,52,,35,36,102,,,,,,,,,,102,80,,,,37,77,,,46', ',80,,32,101,37,77,31,,46,100,80,,32,101,37,231,31,52,46,100,,,32,,35', '36,31,52,103,,104,,,,,,,52,,,,35,36,,,103,102,104,,,,,,,,,,80,,,,37', '77,102,260,46,,35,36,32,101,103,,31,80,,100,,37,77,,,46,,52,,32,101', '35,36,31,,103,100,104,,,,,,80,52,,,37,77,,,46,,102,,32,,210,,31,,,,', '80,211,,,37,77,52,,46,,,,32,101,215,210,31,,,100,,,,211,213,212,214', '52,203,204,206,205,208,209,,201,202,210,,,,,200,,207,211,217,216,,,203', '204,206,205,208,209,,201,202,215,210,,,,200,,207,,211,213,212,214,203', '204,206,205,208,209,,201,202,,210,,,,200,,207,,211,217,216,,,203,204', '206,205,208,209,,201,202,215,210,,,,200,,207,,211,213,212,214,203,204', '206,205,208,209,,201,202,,,,,,200,,207,,,217,216,,,203,204,206,205,208', '209,,201,202,215,210,,,,200,,207,,211,213,212,214,203,204,206,205,208', '209,,201,202,,,,,,200,,207,,,217,216,,,203,204,206,205,208,209,,201', '202,215,210,,,,200,,207,301,211,213,212,214,,,,,215,210,,,,,,,,211,213', '212,214,,,217,216,,,203,204,206,205,208,209,,201,202,,,,,,200,,207,203', '204,206,205,208,209,,201,202,215,210,,,,200,,207,,211,213,212,214,,', ',,,,,,215,210,,,,,,,,211,213,212,214,,,203,204,206,205,208,209,,201', '202,,,,,,200,,207,217,216,,,203,204,206,205,208,209,,201,202,215,210', ',,,200,,207,,211,213,212,214,,,,,,,,,,,,,,,,,,,,,216,,,203,204,206,205', '208,209,,201,202,215,210,,,,200,,207,,211,213,212,214,,,,,,,,,,,,,,', ',,,,,217,216,,,203,204,206,205,208,209,,201,202,215,210,,,,200,,207', ',211,213,212,214,,,,,215,210,,,,,,,,211,213,212,214,,,217,216,,,203', '204,206,205,208,209,,201,202,,,,,,200,,207,203,204,206,205,208,209,', '201,202,215,210,,,,200,,207,,211,213,212,214,,,,,,,,,,,,,,,,,,,,217', '216,,,203,204,206,205,208,209,,201,202,215,210,,,,200,,207,,211,213', '212,214,,,,,,,,,,,,,,,,,,,,217,216,,,203,204,206,205,208,209,,201,202', ',,,,,200,,207,137,135,139,134,136,,132,140,141,148,179,,131,133,,,,', ',142,130,143,137,135,139,134,136,,132,140,141,148,146,,131,133,,138', '144,,,142,130,143,137,135,139,134,136,,132,140,141,148,146,,131,133', ',138,144,,,142,130,143,137,135,139,134,136,,132,140,141,148,179,,131', '133,,138,144,,,142,130,143,137,135,139,134,136,,132,140,141,148,179', ',131,133,,138,144,,,142,130,143,137,135,139,134,136,,132,140,141,148', '146,,131,133,,138,144,,,142,130,143,,,,,,,,,,,,,,,,138,144' ] racc_action_table = arr = ::Array.new(2588, 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,97,97,115,77,262,28,168,28,186,354,313,345,354,97,97,128,186,128', '313,168,28,77,97,97,97,97,0,115,0,0,178,0,0,128,0,0,0,177,0,0,0,391', '391,0,28,97,97,0,391,97,97,97,97,97,97,254,97,97,0,262,262,262,262,97', '0,97,304,304,191,391,68,391,391,304,391,391,49,391,391,391,49,391,391', '391,2,2,391,68,33,153,391,259,259,315,315,191,191,33,33,153,153,391', '154,154,304,259,175,391,153,153,153,153,2,263,2,2,154,2,2,264,2,2,2', '275,2,2,2,229,229,2,173,153,153,2,229,153,153,153,153,153,153,266,153', '153,2,104,104,275,275,153,2,153,104,222,171,229,222,229,229,170,229', '229,84,229,229,229,34,229,229,229,383,383,229,84,34,195,229,383,84,269', '29,288,288,104,29,195,195,229,288,310,270,29,310,229,195,195,195,195', '383,169,383,383,166,383,383,50,383,383,383,50,383,383,383,382,382,383', '185,195,195,383,382,195,195,195,195,195,195,185,195,195,383,51,185,290', '290,195,383,195,289,289,290,382,248,382,382,289,382,382,272,382,382', '382,165,382,382,382,372,372,382,272,85,326,382,372,272,326,246,164,326', '51,261,85,51,382,261,261,85,221,163,382,248,221,221,248,372,162,372', '372,201,372,372,51,372,372,372,155,372,372,372,81,81,372,248,81,246', '372,149,246,192,287,287,146,192,192,287,287,372,145,114,113,160,287', '372,287,160,160,246,81,81,81,81,81,81,81,81,81,81,81,112,81,81,306,306', '87,81,83,81,81,81,181,80,79,181,181,292,292,292,292,76,81,75,292,292', '73,81,81,71,307,292,306,292,306,306,309,306,306,249,306,306,306,69,306', '306,306,319,319,306,67,202,66,306,319,291,291,291,291,64,320,321,291', '291,306,55,55,323,324,291,306,291,53,245,244,319,48,319,319,41,319,319', '343,319,319,319,255,319,319,319,327,327,319,30,27,25,319,327,55,55,243', '23,55,55,357,55,55,319,60,60,55,360,22,319,55,362,21,364,327,366,327', '327,369,327,327,55,327,327,327,370,327,327,327,228,228,327,241,240,376', '327,228,60,60,235,225,60,60,231,60,60,327,1,394,60,396,,327,60,,,,228', ',228,228,,228,228,60,228,228,228,,228,228,228,108,,228,,,,228,,,108', '108,20,20,20,20,,,228,108,108,108,108,,228,294,294,294,294,294,294,', '294,294,19,19,19,19,,294,,294,108,108,296,,108,108,108,108,108,108,296', '108,108,105,5,5,5,5,108,,108,,105,105,9,9,9,9,,,,105,105,105,105,,,296', '296,296,296,296,296,,296,296,7,7,7,7,,296,,296,105,105,,,105,105,105', '105,105,105,,105,105,107,,,,,105,,105,,107,107,286,286,,,,286,286,107', '107,107,107,,286,,286,,,,204,204,,,204,,204,,,,,,107,107,,,107,107,107', '107,107,107,204,107,107,36,36,,,36,107,36,107,204,,,,204,204,,,204,', ',,204,204,36,,204,38,38,204,,38,,38,,36,,204,,36,36,,,36,,,,36,36,38', ',36,39,39,36,,39,,39,,38,,36,,38,38,,,38,,,,38,38,39,,38,40,40,38,,40', ',40,,39,,38,,39,39,,,39,,,,39,39,40,,39,214,214,39,,214,,214,,40,,39', ',40,40,,,40,,,,40,40,214,,40,213,213,40,,213,,213,,214,,40,,214,214', ',,214,,,,214,214,213,,214,212,212,214,,212,,212,,213,,214,,213,213,', ',213,,,,213,213,212,,213,211,211,213,,211,,211,,212,,213,,212,212,,', '212,,,,212,212,211,,212,210,210,212,,210,,210,,211,,212,,211,211,,,211', ',,,211,211,210,,211,209,209,211,,209,,209,,210,,211,,210,210,,,210,', ',,210,210,209,,210,356,356,210,,356,,356,,209,,210,,209,209,,,209,,', ',209,209,356,,209,63,63,209,,63,,63,,356,,209,,356,356,,,356,361,361', ',356,356,63,63,356,,,356,,,317,317,,63,317,356,,63,63,,,63,,,,63,63', '208,208,63,361,208,63,208,361,361,,,361,,63,,361,317,,,361,317,317,208', ',317,207,207,,317,207,361,207,317,208,,311,311,208,208,,,208,311,317', ',208,208,207,,208,,,208,363,363,,,,207,,208,,207,207,,,207,,311,,207', '207,311,311,207,,311,207,,,311,,,,311,207,363,,,,363,363,,,363,311,', ',363,305,305,,363,305,,305,,,,,,,,363,,,206,206,,,206,305,206,,,,,,', ',,,305,,,,305,305,206,,305,,,,305,305,,,305,206,,305,,206,206,,,206', ',305,,206,206,205,205,206,,205,206,205,,,,215,215,,206,215,,215,,,,78', '78,205,,78,78,78,,,,,,215,205,,,,205,205,,,205,78,215,,205,205,215,215', '205,,215,205,78,,215,215,78,78,215,205,78,215,,,78,78,203,203,78,215', '203,78,203,,,,371,371,,78,371,,371,,,,200,200,203,,200,,200,,,,,,371', '203,,,,203,203,,,203,200,371,,203,203,371,371,203,,371,203,200,,371', '371,200,200,371,203,200,371,,,200,200,199,199,200,371,199,200,199,,', ',26,26,,200,26,,26,,,,251,251,199,,251,,251,,,,,,26,199,,,,199,199,', ',199,251,26,,199,199,26,26,199,,26,199,251,,26,,251,251,26,199,251,', ',,251,251,86,86,251,26,86,251,86,,,,252,252,,251,252,,252,,,,92,92,86', ',92,,92,,,,,,252,86,,,,86,86,,,86,92,252,,86,86,252,252,86,,252,86,92', ',252,252,92,92,252,86,92,252,,,92,92,93,93,92,252,93,92,93,,,,94,94', ',92,94,,94,,,,95,95,93,,95,,95,,,,,,94,93,,,,93,93,,,93,95,94,,93,93', '94,94,93,,94,93,95,,94,94,95,95,94,93,95,94,,,95,95,216,216,95,94,216', '95,216,,,,100,100,,95,100,,100,,,,101,101,216,,101,,101,,,,,,100,216', ',,,216,216,,,216,101,100,,216,216,100,100,216,,100,216,101,,100,100', '101,101,100,216,101,100,,,101,101,102,102,101,100,102,101,102,,,,103', '103,,101,103,103,103,,,,256,256,102,,256,,256,,,,,,103,102,,,,102,102', ',,102,256,103,,102,102,103,103,102,,103,102,256,,103,103,256,256,103', '102,256,103,,,256,256,217,217,256,103,217,256,217,,,,234,234,,256,,', ',,,,109,109,217,,,,109,,,,265,265,,217,265,,,217,217,,,217,,234,,217', '217,234,234,217,,234,217,109,,234,,109,109,234,217,109,,265,,109,,265', '265,109,234,265,276,276,,265,276,,276,265,109,,277,277,,,277,,277,,265', ',230,230,276,,,,,,,,,,277,276,,,,276,276,,,276,,277,,276,276,277,277', '276,,277,276,230,,277,277,230,230,277,276,230,277,,,230,,159,159,230', '277,159,,159,,,,,,,230,,,,158,158,,,158,159,158,,,,,,,,,,159,,,,159', '159,158,158,159,,157,157,159,159,157,,159,158,,159,,158,158,,,158,,159', ',158,158,62,62,158,,62,158,62,,,,,,157,158,,,157,157,,,157,,62,,157', ',298,,157,,,,,62,298,,,62,62,157,,62,,,,62,62,335,335,62,,,62,,,,335', '335,335,335,62,298,298,298,298,298,298,,298,298,295,,,,,298,,298,295', '335,335,,,335,335,335,335,335,335,,335,335,390,390,,,,335,,335,,390', '390,390,390,295,295,295,295,295,295,,295,295,,297,,,,295,,295,,297,390', '390,,,390,390,390,390,390,390,,390,390,156,156,,,,390,,390,,156,156', '156,156,297,297,297,297,297,297,,297,297,,,,,,297,,297,,,156,156,,,156', '156,156,156,156,156,,156,156,194,194,,,,156,,156,,194,194,194,194,293', '293,293,293,293,293,,293,293,,,,,,293,,293,,,194,194,,,194,194,194,194', '194,194,,194,194,220,220,,,,194,,194,220,220,220,220,220,,,,,348,348', ',,,,,,,348,348,348,348,,,220,220,,,220,220,220,220,220,220,,220,220', ',,,,,220,,220,348,348,348,348,348,348,,348,348,349,349,,,,348,,348,', '349,349,349,349,,,,,,,,,333,333,,,,,,,,333,333,333,333,,,349,349,349', '349,349,349,,349,349,,,,,,349,,349,333,333,,,333,333,333,333,333,333', ',333,333,300,300,,,,333,,333,,300,300,300,300,,,,,,,,,,,,,,,,,,,,,300', ',,300,300,300,300,300,300,,300,300,352,352,,,,300,,300,,352,352,352', '352,,,,,,,,,,,,,,,,,,,,352,352,,,352,352,352,352,352,352,,352,352,196', '196,,,,352,,352,,196,196,196,196,,,,,299,299,,,,,,,,299,299,299,299', ',,196,196,,,196,196,196,196,196,196,,196,196,,,,,,196,,196,299,299,299', '299,299,299,,299,299,193,193,,,,299,,299,,193,193,193,193,,,,,,,,,,', ',,,,,,,,,193,193,,,193,193,193,193,193,193,,193,193,332,332,,,,193,', '193,,332,332,332,332,,,,,,,,,,,,,,,,,,,,332,332,,,332,332,332,332,332', '332,,332,332,,,,,,332,,332,268,268,268,268,268,,268,268,268,268,268', ',268,268,,,,,,268,268,268,180,180,180,180,180,,180,180,180,180,180,', '180,180,,268,268,,,180,180,180,61,61,61,61,61,,61,61,61,61,61,,61,61', ',180,180,,,61,61,61,267,267,267,267,267,,267,267,267,267,267,,267,267', ',61,61,,,267,267,267,82,82,82,82,82,,82,82,82,82,82,,82,82,,267,267', ',,82,82,82,250,250,250,250,250,,250,250,250,250,250,,250,250,,82,82', ',,250,250,250,,,,,,,,,,,,,,,,250,250' ] racc_action_check = arr = ::Array.new(2588, 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, 490, 84, nil, nil, 507, nil, 539, nil, 517, nil, nil, nil, nil, nil, nil, nil, nil, nil, 485, 463, 447, 428, 417, nil, 428, 1304, 425, 1, 146, 388, nil, nil, 84, 153, nil, 675, nil, 700, 725, 750, 395, nil, nil, nil, nil, nil, nil, 413, 42, 171, 231, nil, 411, nil, 402, nil, nil, nil, nil, 445, 2453, 1832, 950, 386, nil, 368, 366, 66, 359, nil, 345, nil, 359, nil, 339, 337, -1, 1180, 330, 346, 299, 2497, 339, 140, 238, 1361, 337, nil, nil, nil, nil, 1381, 1428, 1438, 1448, nil, -2, nil, nil, 1505, 1515, 1562, 1572, 145, 561, nil, 615, 507, 1649, nil, nil, 328, 297, 296, -8, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 11, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 307, 261, nil, nil, 297, nil, nil, nil, 84, 93, 274, 1954, 1811, 1785, 1768, 315, nil, 264, 257, 228, 242, 184, nil, -3, 178, 138, 133, nil, 109, nil, 102, nil, 16, 20, nil, 2431, 305, nil, nil, nil, 194, -8, nil, nil, nil, nil, 48, 303, 2324, 1999, 170, 2262, nil, nil, 1294, 1247, 228, 328, 1227, 650, 1160, 1113, 1022, 997, 900, 875, 850, 825, 800, 775, 1170, 1495, 1629, nil, nil, 2044, 272, 146, nil, nil, 472, nil, nil, 470, 127, 1721, 462, nil, nil, 1639, 471, nil, nil, nil, nil, 474, 467, nil, 404, 376, 404, 266, nil, 242, 369, 2519, 1314, 1371, nil, 34, 400, 1582, nil, nil, 82, nil, 265, -2, 108, 112, 1659, 134, 2475, 2409, 172, 180, nil, 226, nil, nil, 100, 1701, 1711, nil, nil, nil, nil, nil, nil, nil, nil, 579, 256, 123, 180, 175, 341, 303, 1976, 486, 1886, 540, 1931, 1842, 2279, 2172, nil, nil, nil, 66, 1096, 341, 360, nil, 366, 160, 1032, nil, 0, nil, 84, nil, 980, nil, 384, 374, 388, nil, 391, 399, nil, 227, 427, nil, nil, nil, nil, 2369, 2127, nil, 1864, nil, nil, nil, nil, nil, nil, nil, 409, nil, 4, nil, nil, 2061, 2106, nil, nil, 2217, nil, -20, nil, 925, 435, nil, nil, 442, 967, 445, 1050, 447, nil, 449, nil, nil, 436, 433, 1237, 256, nil, nil, nil, 468, nil, nil, nil, nil, nil, 213, 170, nil, nil, nil, nil, nil, nil, 1909, 41, nil, nil, 482, nil, 484, nil, nil, nil, nil ] racc_action_default = [ -206, -241, -1, -2, -3, -6, -7, -8, -9, -10, -11, -12, -13, -14, -15, -16, -17, -18, -19, -20, -241, -39, -41, -42, -43, -45, -97, -241, -178, -241, -74, -96, -98, -221, -239, -124, -241, -129, -241, -241, -241, -241, -177, -181, -182, -183, -185, -186, -241, -241, -198, -241, -229, -241, -4, -241, -46, -47, -48, -49, -241, -119, -241, -241, -53, -54, -58, -59, -60, -61, -62, -63, -64, -65, -66, -67, -68, -97, -241, -239, -221, -109, -109, -77, -206, -206, -241, -241, -73, -197, -198, -75, -241, -241, -241, -241, -125, -241, -141, -142, -241, -241, -241, -241, -241, -241, -134, -241, -241, -241, -187, -188, -190, -206, -206, -206, -199, -201, -202, -203, -204, -205, 401, -37, -38, -39, -40, -44, -97, -36, -21, -22, -23, -24, -25, -26, -27, -28, -29, -30, -31, -32, -33, -34, -35, -227, -112, -113, -114, -241, -117, -118, -120, -241, -241, -52, -56, -241, -241, -241, -241, -224, -24, -34, -94, -227, -241, -92, -97, -99, -100, -101, -102, -103, -104, -105, -110, -114, -227, -112, -119, -241, -80, -81, -83, -206, -241, -89, -90, -97, -221, -241, -241, -106, -108, -241, -107, -126, -127, -241, -241, -241, -241, -241, -241, -241, -241, -241, -241, -241, -241, -241, -241, -241, -241, -241, -241, -241, -153, -160, -241, -241, -241, -232, -233, -241, -236, -237, -241, -241, -241, -97, -171, -172, -241, -241, -178, -179, -180, -184, -241, -241, -208, -207, -206, -241, -241, -215, -241, -241, -228, -241, -241, -240, -50, -226, -241, -225, -55, -241, -123, -241, -222, -226, -241, -95, -241, -228, -109, -241, -227, -78, -241, -85, -86, -241, -241, -241, -79, -131, -226, -238, -128, -143, -144, -145, -146, -147, -148, -149, -150, -151, -152, -154, -155, -156, -157, -158, -159, -161, -162, -163, -222, -230, -241, -241, -5, -241, -133, -241, -137, -241, -165, -241, -169, -227, -174, -241, -189, -241, -241, -227, -211, -214, -241, -217, -241, -241, -200, -216, -72, -121, -116, -115, -51, -57, -122, -130, -223, -69, -93, -70, -111, -227, -71, -241, -82, -84, -87, -88, -231, -234, -235, -132, -137, -136, -241, -241, -164, -166, -241, -241, -241, -241, -226, -176, -241, -192, -209, -241, -228, -241, -241, -218, -219, -220, -241, -196, -91, -76, -135, -138, -241, -241, -170, -173, -175, -191, -210, -212, -213, -241, -194, -195, -241, -140, -241, -168, -193, -139, -167 ] racc_goto_table = [ 28, 2, 28, 42, 224, 42, 54, 65, 106, 233, 113, 114, 235, 121, 116, 44, 174, 44, 43, 111, 43, 249, 167, 96, 307, 309, 3, 322, 145, 87, 25, 88, 25, 165, 178, 176, 176, 83, 312, 355, 123, 266, 181, 191, 311, 129, 127, 331, 126, 154, 24, 127, 24, 126, 269, 28, 346, 124, 42, 232, 28, 197, 124, 42, 160, 55, 60, 241, 244, 315, 44, 264, 192, 43, 112, 44, 164, 324, 43, 115, 245, 171, 363, 380, 170, 25, 329, 188, 188, 221, 25, 320, 321, 64, 373, 222, 44, 1, nil, 43, nil, nil, nil, nil, nil, 24, nil, nil, nil, 236, 24, 175, 42, nil, nil, 366, nil, nil, 34, 359, 34, nil, nil, 376, 44, nil, nil, 43, nil, nil, nil, 172, nil, 314, 316, nil, nil, 235, 258, 242, 242, 247, nil, 275, 259, 261, 345, 270, nil, nil, nil, nil, nil, nil, 389, nil, nil, nil, nil, nil, nil, nil, nil, 284, 285, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 394, 396, nil, nil, nil, nil, nil, nil, 282, nil, 188, nil, nil, 362, nil, nil, nil, nil, nil, 369, nil, nil, 174, nil, nil, nil, 351, nil, 340, nil, 121, 328, 121, 318, nil, nil, 314, nil, nil, nil, nil, 378, 343, 342, 176, nil, nil, nil, nil, 239, 28, 28, 236, 42, 42, 42, 236, nil, nil, 42, nil, nil, nil, nil, nil, 44, 44, 44, 43, 43, 43, 44, nil, nil, 43, nil, nil, nil, nil, nil, 25, 25, nil, nil, nil, 386, 384, 171, 235, nil, 170, nil, 325, nil, nil, nil, nil, 188, nil, nil, 24, 24, 44, nil, nil, 43, 23, nil, 23, 374, nil, nil, nil, nil, nil, nil, nil, 175, nil, nil, 365, 45, nil, 45, nil, nil, nil, nil, 28, nil, nil, 42, 54, 236, nil, nil, 42, 172, nil, nil, nil, 28, nil, 44, 42, nil, 43, 381, 44, 28, nil, 43, 42, nil, nil, nil, 44, nil, 25, 43, nil, 23, nil, nil, 44, nil, 23, 43, 34, 34, 239, 25, nil, nil, 239, nil, 45, nil, 24, 25, nil, 45, nil, 236, nil, 236, 42, 169, 42, nil, 22, 24, 22, nil, 28, 391, nil, 42, 44, 24, 44, 43, 45, 43, 28, 28, nil, 42, 42, 44, 227, nil, 43, 28, nil, 237, 42, 54, nil, 44, 44, nil, 43, 43, 25, 21, nil, 21, 44, nil, 45, 43, nil, nil, 25, 25, nil, nil, nil, nil, nil, nil, nil, 25, 24, 22, 34, nil, nil, nil, 22, 239, nil, nil, 24, 24, nil, nil, nil, 34, nil, nil, nil, 24, nil, nil, nil, 34, nil, nil, nil, 173, nil, nil, 187, 187, nil, nil, nil, nil, 125, nil, nil, nil, nil, 125, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 238, nil, 239, nil, 239, nil, nil, nil, nil, nil, nil, nil, nil, 34, nil, nil, nil, nil, nil, nil, nil, nil, nil, 34, 34, nil, nil, nil, nil, nil, nil, nil, 34, nil, nil, 23, 23, 237, nil, nil, nil, 237, nil, nil, nil, nil, nil, nil, nil, nil, 45, 45, 45, nil, nil, nil, 45, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 169, nil, nil, nil, 187, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 45, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 227, nil, 23, nil, nil, nil, nil, 237, 22, 22, 238, nil, nil, nil, 238, 23, nil, 45, nil, nil, nil, nil, 45, 23, nil, nil, nil, nil, nil, nil, 45, nil, nil, nil, nil, nil, nil, nil, 45, nil, nil, nil, nil, 21, 21, 173, nil, 97, nil, 105, 107, 108, 187, nil, nil, nil, nil, 237, nil, 237, nil, nil, nil, nil, nil, nil, nil, nil, 23, nil, nil, nil, 45, 153, 45, nil, nil, nil, 23, 23, nil, nil, nil, 45, nil, nil, 22, 23, nil, nil, nil, 238, nil, 45, 45, nil, nil, nil, nil, 22, nil, nil, 45, 193, 194, 195, 196, 22, nil, nil, nil, 218, 219, 220, nil, nil, nil, nil, nil, nil, nil, 21, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 21, nil, nil, nil, nil, nil, nil, 238, 21, 238, nil, nil, nil, nil, nil, nil, nil, nil, 22, nil, nil, nil, nil, nil, nil, nil, nil, nil, 22, 22, nil, nil, nil, nil, nil, nil, nil, 22, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 21, nil, nil, nil, nil, nil, nil, nil, nil, nil, 21, 21, nil, nil, nil, nil, nil, nil, nil, 21, nil, nil, nil, 97, 283, nil, nil, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 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, 332, 333, nil, nil, nil, 335, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 348, 349, 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, 352, 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, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 107, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 390 ] racc_goto_check = [ 35, 2, 35, 34, 86, 34, 4, 31, 62, 71, 40, 40, 69, 65, 79, 36, 37, 36, 38, 73, 38, 44, 53, 60, 5, 5, 3, 83, 45, 35, 28, 6, 28, 43, 43, 56, 56, 47, 67, 63, 22, 44, 48, 48, 66, 22, 10, 59, 8, 29, 27, 10, 27, 8, 44, 35, 49, 6, 34, 70, 35, 61, 6, 34, 29, 23, 23, 75, 75, 72, 36, 42, 29, 38, 74, 36, 41, 76, 38, 77, 78, 35, 33, 63, 34, 28, 80, 34, 34, 29, 28, 81, 82, 30, 84, 85, 36, 1, nil, 38, nil, nil, nil, nil, nil, 27, nil, nil, nil, 35, 27, 28, 34, nil, nil, 5, nil, nil, 55, 67, 55, nil, nil, 5, 36, nil, nil, 38, nil, nil, nil, 27, nil, 69, 71, nil, nil, 69, 31, 3, 3, 3, nil, 48, 29, 29, 44, 45, nil, nil, nil, nil, nil, nil, 83, nil, nil, nil, nil, nil, nil, nil, nil, 65, 65, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 5, 5, nil, nil, nil, nil, nil, nil, 60, nil, 34, nil, nil, 44, nil, nil, nil, nil, nil, 44, nil, nil, 37, nil, nil, nil, 86, nil, 53, nil, 65, 79, 65, 73, nil, nil, 69, nil, nil, nil, nil, 44, 43, 56, 56, nil, nil, nil, nil, 55, 35, 35, 35, 34, 34, 34, 35, nil, nil, 34, nil, nil, nil, nil, nil, 36, 36, 36, 38, 38, 38, 36, nil, nil, 38, nil, nil, nil, nil, nil, 28, 28, nil, nil, nil, 71, 69, 35, 69, nil, 34, nil, 3, nil, nil, nil, nil, 34, nil, nil, 27, 27, 36, nil, nil, 38, 26, nil, 26, 40, nil, nil, nil, nil, nil, nil, nil, 28, nil, nil, 31, 39, nil, 39, nil, nil, nil, nil, 35, nil, nil, 34, 4, 35, nil, nil, 34, 27, nil, nil, nil, 35, nil, 36, 34, nil, 38, 62, 36, 35, nil, 38, 34, nil, nil, nil, 36, nil, 28, 38, nil, 26, nil, nil, 36, nil, 26, 38, 55, 55, 55, 28, nil, nil, 55, nil, 39, nil, 27, 28, nil, 39, nil, 35, nil, 35, 34, 26, 34, nil, 25, 27, 25, nil, 35, 2, nil, 34, 36, 27, 36, 38, 39, 38, 35, 35, nil, 34, 34, 36, 26, nil, 38, 35, nil, 26, 34, 4, nil, 36, 36, nil, 38, 38, 28, 24, nil, 24, 36, nil, 39, 38, nil, nil, 28, 28, nil, nil, nil, nil, nil, nil, nil, 28, 27, 25, 55, nil, nil, nil, 25, 55, nil, nil, 27, 27, nil, nil, nil, 55, nil, nil, nil, 27, nil, nil, nil, 55, nil, nil, nil, 25, nil, nil, 25, 25, nil, nil, nil, nil, 24, nil, nil, nil, nil, 24, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 25, nil, 55, nil, 55, nil, nil, nil, nil, nil, nil, nil, nil, 55, nil, nil, nil, nil, nil, nil, nil, nil, nil, 55, 55, nil, nil, nil, nil, nil, nil, nil, 55, nil, nil, 26, 26, 26, nil, nil, nil, 26, nil, nil, nil, nil, nil, nil, nil, nil, 39, 39, 39, nil, nil, nil, 39, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 26, nil, nil, nil, 25, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 39, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 26, nil, 26, nil, nil, nil, nil, 26, 25, 25, 25, nil, nil, nil, 25, 26, nil, 39, nil, nil, nil, nil, 39, 26, nil, nil, nil, nil, nil, nil, 39, nil, nil, nil, nil, nil, nil, nil, 39, nil, nil, nil, nil, 24, 24, 25, nil, 32, nil, 32, 32, 32, 25, nil, nil, nil, nil, 26, nil, 26, nil, nil, nil, nil, nil, nil, nil, nil, 26, nil, nil, nil, 39, 32, 39, nil, nil, nil, 26, 26, nil, nil, nil, 39, nil, nil, 25, 26, nil, nil, nil, 25, nil, 39, 39, nil, nil, nil, nil, 25, nil, nil, 39, 32, 32, 32, 32, 25, nil, nil, nil, 32, 32, 32, nil, nil, nil, nil, nil, nil, nil, 24, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 24, nil, nil, nil, nil, nil, nil, 25, 24, 25, nil, nil, nil, nil, nil, nil, nil, nil, 25, nil, nil, nil, nil, nil, nil, nil, nil, nil, 25, 25, nil, nil, nil, nil, nil, nil, nil, 25, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 24, nil, nil, nil, nil, nil, nil, nil, nil, nil, 24, 24, nil, nil, nil, nil, nil, nil, nil, 24, nil, nil, nil, 32, 32, nil, nil, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 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, 32, 32, nil, nil, nil, 32, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 32, 32, 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, 32, 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, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 32, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 32 ] racc_goto_pointer = [ nil, 97, 1, 26, 4, -204, 2, nil, -7, nil, -9, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, -15, 46, 403, 368, 284, 50, 30, -14, 67, -19, 599, -233, 3, 0, 15, -65, 18, 299, -39, -5, -93, -48, -124, -33, nil, 9, -42, -216, nil, nil, nil, -59, nil, 118, -46, nil, nil, -203, -13, -36, -31, -271, nil, -38, -186, -192, nil, -97, -50, -100, -165, -29, 26, -46, -167, 28, -35, -37, -162, -152, -151, -216, -232, -9, -100, nil ] racc_goto_default = [ nil, nil, 306, 182, 4, nil, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 147, 20, nil, 74, 71, 66, 70, 73, nil, nil, 98, 156, 256, 67, 68, 69, 72, 75, 76, 27, nil, nil, nil, nil, nil, 29, nil, nil, 183, 272, 184, 186, nil, 166, 79, 150, 149, 151, 152, nil, nil, nil, nil, 99, 47, nil, nil, 313, 41, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 117, nil, nil, nil, nil, nil, nil, 225 ] racc_reduce_table = [ 0, 0, :racc_error, 1, 71, :_reduce_none, 1, 71, :_reduce_none, 1, 72, :_reduce_3, 2, 72, :_reduce_4, 1, 75, :_reduce_5, 1, 74, :_reduce_none, 1, 74, :_reduce_none, 1, 74, :_reduce_none, 1, 74, :_reduce_none, 1, 74, :_reduce_none, 1, 74, :_reduce_none, 1, 74, :_reduce_none, 1, 74, :_reduce_none, 1, 74, :_reduce_none, 1, 74, :_reduce_none, 1, 74, :_reduce_none, 1, 74, :_reduce_none, 1, 74, :_reduce_none, 1, 74, :_reduce_none, 1, 74, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, 3, 90, :_reduce_36, 3, 90, :_reduce_37, 1, 92, :_reduce_none, 1, 92, :_reduce_none, 1, 92, :_reduce_none, 1, 92, :_reduce_none, 1, 92, :_reduce_none, 1, 92, :_reduce_none, 1, 92, :_reduce_none, 1, 92, :_reduce_none, 1, 93, :_reduce_none, 1, 93, :_reduce_none, 1, 93, :_reduce_none, 1, 93, :_reduce_none, 4, 84, :_reduce_50, 5, 84, :_reduce_51, 3, 84, :_reduce_52, 2, 84, :_reduce_53, 1, 100, :_reduce_54, 3, 100, :_reduce_55, 1, 99, :_reduce_56, 3, 99, :_reduce_57, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 5, 76, :_reduce_69, 5, 76, :_reduce_70, 5, 76, :_reduce_71, 5, 88, :_reduce_72, 2, 77, :_reduce_73, 1, 116, :_reduce_74, 2, 116, :_reduce_75, 6, 78, :_reduce_76, 2, 78, :_reduce_77, 3, 117, :_reduce_78, 3, 117, :_reduce_79, 1, 118, :_reduce_none, 1, 118, :_reduce_none, 3, 118, :_reduce_82, 1, 119, :_reduce_none, 3, 119, :_reduce_84, 1, 120, :_reduce_85, 1, 120, :_reduce_86, 3, 121, :_reduce_87, 3, 121, :_reduce_88, 1, 122, :_reduce_none, 1, 122, :_reduce_none, 4, 123, :_reduce_91, 1, 111, :_reduce_92, 3, 111, :_reduce_93, 0, 112, :_reduce_none, 1, 112, :_reduce_none, 1, 109, :_reduce_96, 1, 104, :_reduce_97, 1, 105, :_reduce_98, 1, 124, :_reduce_none, 1, 124, :_reduce_none, 1, 124, :_reduce_none, 1, 124, :_reduce_none, 1, 124, :_reduce_none, 1, 124, :_reduce_none, 1, 124, :_reduce_none, 3, 79, :_reduce_106, 3, 79, :_reduce_107, 3, 89, :_reduce_108, 0, 113, :_reduce_109, 1, 113, :_reduce_110, 3, 113, :_reduce_111, 1, 127, :_reduce_none, 1, 127, :_reduce_none, 1, 127, :_reduce_none, 3, 126, :_reduce_115, 3, 128, :_reduce_116, 1, 129, :_reduce_none, 1, 129, :_reduce_none, 0, 115, :_reduce_119, 1, 115, :_reduce_120, 3, 115, :_reduce_121, 4, 108, :_reduce_122, 3, 108, :_reduce_123, 1, 96, :_reduce_124, 2, 96, :_reduce_125, 2, 130, :_reduce_126, 1, 131, :_reduce_127, 2, 131, :_reduce_128, 1, 106, :_reduce_129, 4, 94, :_reduce_130, 4, 94, :_reduce_131, 5, 82, :_reduce_132, 4, 82, :_reduce_133, 2, 81, :_reduce_134, 5, 132, :_reduce_135, 4, 132, :_reduce_136, 0, 133, :_reduce_none, 2, 133, :_reduce_138, 4, 133, :_reduce_139, 3, 133, :_reduce_140, 1, 102, :_reduce_none, 1, 102, :_reduce_none, 3, 102, :_reduce_143, 3, 102, :_reduce_144, 3, 102, :_reduce_145, 3, 102, :_reduce_146, 3, 102, :_reduce_147, 3, 102, :_reduce_148, 3, 102, :_reduce_149, 3, 102, :_reduce_150, 3, 102, :_reduce_151, 3, 102, :_reduce_152, 2, 102, :_reduce_153, 3, 102, :_reduce_154, 3, 102, :_reduce_155, 3, 102, :_reduce_156, 3, 102, :_reduce_157, 3, 102, :_reduce_158, 3, 102, :_reduce_159, 2, 102, :_reduce_160, 3, 102, :_reduce_161, 3, 102, :_reduce_162, 3, 102, :_reduce_163, 5, 80, :_reduce_164, 1, 136, :_reduce_165, 2, 136, :_reduce_166, 5, 137, :_reduce_167, 4, 137, :_reduce_168, 1, 138, :_reduce_169, 3, 138, :_reduce_170, 3, 97, :_reduce_171, 1, 140, :_reduce_none, 4, 140, :_reduce_173, 1, 142, :_reduce_none, 3, 142, :_reduce_175, 3, 141, :_reduce_176, 1, 139, :_reduce_none, 1, 139, :_reduce_none, 1, 139, :_reduce_none, 1, 139, :_reduce_none, 1, 139, :_reduce_none, 1, 139, :_reduce_none, 1, 139, :_reduce_none, 1, 139, :_reduce_none, 1, 139, :_reduce_185, 1, 139, :_reduce_none, 1, 143, :_reduce_187, 1, 144, :_reduce_none, 3, 144, :_reduce_189, 2, 83, :_reduce_190, 6, 85, :_reduce_191, 5, 85, :_reduce_192, 7, 86, :_reduce_193, 6, 86, :_reduce_194, 6, 87, :_reduce_195, 5, 87, :_reduce_196, 1, 110, :_reduce_197, 1, 110, :_reduce_198, 1, 147, :_reduce_199, 3, 147, :_reduce_200, 1, 149, :_reduce_201, 1, 150, :_reduce_202, 1, 150, :_reduce_203, 1, 150, :_reduce_204, 1, 150, :_reduce_none, 0, 73, :_reduce_206, 0, 151, :_reduce_207, 1, 145, :_reduce_none, 3, 145, :_reduce_209, 4, 145, :_reduce_210, 1, 152, :_reduce_none, 3, 152, :_reduce_212, 3, 153, :_reduce_213, 1, 153, :_reduce_214, 1, 148, :_reduce_none, 2, 148, :_reduce_216, 1, 146, :_reduce_none, 2, 146, :_reduce_218, 1, 154, :_reduce_none, 1, 154, :_reduce_none, 1, 95, :_reduce_221, 3, 107, :_reduce_222, 4, 107, :_reduce_223, 2, 107, :_reduce_224, 1, 103, :_reduce_none, 1, 103, :_reduce_none, 0, 114, :_reduce_none, 1, 114, :_reduce_228, 1, 135, :_reduce_229, 3, 134, :_reduce_230, 4, 134, :_reduce_231, 2, 134, :_reduce_232, 1, 155, :_reduce_none, 3, 155, :_reduce_234, 3, 156, :_reduce_235, 1, 157, :_reduce_236, 1, 157, :_reduce_237, 4, 125, :_reduce_238, 1, 98, :_reduce_none, 4, 98, :_reduce_240 ] racc_reduce_n = 241 racc_shift_n = 401 racc_token_table = { false => 0, :error => 1, :STRING => 2, :DQPRE => 3, :DQMID => 4, :DQPOST => 5, :LBRACK => 6, :RBRACK => 7, :LBRACE => 8, :RBRACE => 9, :SYMBOL => 10, :FARROW => 11, :COMMA => 12, :TRUE => 13, :FALSE => 14, :EQUALS => 15, :APPENDS => 16, :LESSEQUAL => 17, :NOTEQUAL => 18, :DOT => 19, :COLON => 20, :LLCOLLECT => 21, :RRCOLLECT => 22, :QMARK => 23, :LPAREN => 24, :RPAREN => 25, :ISEQUAL => 26, :GREATEREQUAL => 27, :GREATERTHAN => 28, :LESSTHAN => 29, :IF => 30, :ELSE => 31, :IMPORT => 32, :DEFINE => 33, :ELSIF => 34, :VARIABLE => 35, :CLASS => 36, :INHERITS => 37, :NODE => 38, :BOOLEAN => 39, :NAME => 40, :SEMIC => 41, :CASE => 42, :DEFAULT => 43, :AT => 44, :LCOLLECT => 45, :RCOLLECT => 46, :CLASSREF => 47, :NOT => 48, :OR => 49, :AND => 50, :UNDEF => 51, :PARROW => 52, :PLUS => 53, :MINUS => 54, :TIMES => 55, :DIV => 56, :LSHIFT => 57, :RSHIFT => 58, :UMINUS => 59, :MATCH => 60, :NOMATCH => 61, :REGEX => 62, :IN_EDGE => 63, :OUT_EDGE => 64, :IN_EDGE_SUB => 65, :OUT_EDGE_SUB => 66, :IN => 67, :UNLESS => 68, :MODULO => 69 } racc_nt_base = 70 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", "LBRACK", "RBRACK", "LBRACE", "RBRACE", "SYMBOL", "FARROW", "COMMA", "TRUE", "FALSE", "EQUALS", "APPENDS", "LESSEQUAL", "NOTEQUAL", "DOT", "COLON", "LLCOLLECT", "RRCOLLECT", "QMARK", "LPAREN", "RPAREN", "ISEQUAL", "GREATEREQUAL", "GREATERTHAN", "LESSTHAN", "IF", "ELSE", "IMPORT", "DEFINE", "ELSIF", "VARIABLE", "CLASS", "INHERITS", "NODE", "BOOLEAN", "NAME", "SEMIC", "CASE", "DEFAULT", "AT", "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", "MODULO", "$start", "program", "statements_and_declarations", "nil", "statement_or_declaration", "statements", "resource", "virtualresource", "collection", "assignment", "casestatement", "ifstatement_begin", "unlessstatement", "import", "fstatement", "definition", "hostclass", "nodedef", "resourceoverride", "append", "relationship", "keyword", "relationship_side", "edge", "resourceref", "variable", "quotedtext", "selector", "hasharrayaccesses", "expressions", "funcvalues", "rvalue", "expression", "comma", "name", "type", "boolean", "array", "funcrvalue", "undef", "classname", "resourceinstances", "endsemi", "params", "endcomma", "anyparams", "at", "collectrhand", "collstatements", "collstatement", "colljoin", "collexpr", "colllval", "resourceinst", "resourcename", "hasharrayaccess", "param", "param_name", "addparam", "anyparam", "dqrval", "dqtail", "ifstatement", "else", "hash", "regex", "caseopts", "caseopt", "casevalues", "selectlhand", "svalues", "selectval", "sintvalues", "string", "strings", "argumentlist", "classparent", "hostnames", "nodeparent", "nodename", "hostname", "nothing", "arguments", "argument", "classnameordefault", "hashpairs", "hashpair", "key" ] Racc_debug_parser = false ##### State transition tables end ##### # reduce 0 omitted # reduce 1 omitted # reduce 2 omitted module_eval(<<'.,.,', 'grammar.ra', 34) def _reduce_3(val, _values, result) result = ast AST::BlockExpression, :children => (val[0] ? [val[0]] : []) result end .,., module_eval(<<'.,.,', 'grammar.ra', 37) def _reduce_4(val, _values, result) if val[1] val[0].push(val[1]) end result = val[0] result end .,., module_eval(<<'.,.,', 'grammar.ra', 46) def _reduce_5(val, _values, result) val[0].each do |stmt| if stmt.is_a?(AST::TopLevelConstruct) error "Classes, definitions, and nodes may only appear at toplevel or inside other classes", \ :line => stmt.context[:line], :file => stmt.context[:file] end end result = val[0] result end .,., # reduce 6 omitted # reduce 7 omitted # reduce 8 omitted # reduce 9 omitted # reduce 10 omitted # reduce 11 omitted # reduce 12 omitted # reduce 13 omitted # reduce 14 omitted # reduce 15 omitted # reduce 16 omitted # reduce 17 omitted # reduce 18 omitted # reduce 19 omitted # reduce 20 omitted # reduce 21 omitted # reduce 22 omitted # reduce 23 omitted # reduce 24 omitted # reduce 25 omitted # reduce 26 omitted # reduce 27 omitted # reduce 28 omitted # reduce 29 omitted # reduce 30 omitted # reduce 31 omitted # reduce 32 omitted # reduce 33 omitted # reduce 34 omitted # reduce 35 omitted module_eval(<<'.,.,', 'grammar.ra', 89) def _reduce_36(val, _values, result) result = AST::Relationship.new(val[0], val[2], val[1][:value], ast_context) result end .,., module_eval(<<'.,.,', 'grammar.ra', 92) def _reduce_37(val, _values, result) result = AST::Relationship.new(val[0], val[2], val[1][:value], ast_context) result end .,., # reduce 38 omitted # reduce 39 omitted # reduce 40 omitted # reduce 41 omitted # reduce 42 omitted # reduce 43 omitted # reduce 44 omitted # reduce 45 omitted # reduce 46 omitted # reduce 47 omitted # reduce 48 omitted # reduce 49 omitted module_eval(<<'.,.,', 'grammar.ra', 107) def _reduce_50(val, _values, result) result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => val[2], :ftype => :statement result end .,., module_eval(<<'.,.,', 'grammar.ra', 114) def _reduce_51(val, _values, result) result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => val[2], :ftype => :statement result end .,., module_eval(<<'.,.,', 'grammar.ra', 120) def _reduce_52(val, _values, result) result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => AST::ASTArray.new({}), :ftype => :statement result end .,., module_eval(<<'.,.,', 'grammar.ra', 127) def _reduce_53(val, _values, result) result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => val[1], :ftype => :statement result end .,., module_eval(<<'.,.,', 'grammar.ra', 134) def _reduce_54(val, _values, result) result = aryfy(val[0]) result end .,., module_eval(<<'.,.,', 'grammar.ra', 137) def _reduce_55(val, _values, result) val[0].push(val[2]) result = val[0] result end .,., module_eval(<<'.,.,', 'grammar.ra', 141) def _reduce_56(val, _values, result) result = aryfy(val[0]) result end .,., module_eval(<<'.,.,', 'grammar.ra', 142) def _reduce_57(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 58 omitted # reduce 59 omitted # reduce 60 omitted # reduce 61 omitted # reduce 62 omitted # reduce 63 omitted # reduce 64 omitted # reduce 65 omitted # reduce 66 omitted # reduce 67 omitted # reduce 68 omitted module_eval(<<'.,.,', 'grammar.ra', 157) def _reduce_69(val, _values, result) @lexer.commentpop result = ast(AST::Resource, :type => val[0], :instances => val[2]) result end .,., module_eval(<<'.,.,', 'grammar.ra', 160) def _reduce_70(val, _values, result) # This is a deprecated syntax. error "All resource specifications require names" result end .,., module_eval(<<'.,.,', 'grammar.ra', 163) def _reduce_71(val, _values, result) # a defaults setting for a type @lexer.commentpop result = ast(AST::ResourceDefaults, :type => val[0].value, :parameters => val[2]) result end .,., module_eval(<<'.,.,', 'grammar.ra', 170) def _reduce_72(val, _values, result) @lexer.commentpop result = ast AST::ResourceOverride, :object => val[0], :parameters => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 177) def _reduce_73(val, _values, result) type = val[0] if (type == :exported and ! Puppet[:storeconfigs]) Puppet.warning addcontext("You cannot collect without storeconfigs being set") end error "Defaults are not virtualizable" if val[1].is_a? AST::ResourceDefaults method = type.to_s + "=" # Just mark our resource as exported and pass it through. val[1].send(method, true) result = val[1] result end .,., module_eval(<<'.,.,', 'grammar.ra', 193) def _reduce_74(val, _values, result) result = :virtual result end .,., module_eval(<<'.,.,', 'grammar.ra', 194) def _reduce_75(val, _values, result) result = :exported result end .,., module_eval(<<'.,.,', 'grammar.ra', 199) def _reduce_76(val, _values, result) @lexer.commentpop type = val[0].value.downcase args = {:type => type} if val[1].is_a?(AST::CollExpr) args[:query] = val[1] args[:query].type = type args[:form] = args[:query].form else args[:form] = val[1] end if args[:form] == :exported and ! Puppet[:storeconfigs] Puppet.warning addcontext("You cannot collect exported resources without storeconfigs being set; the collection will be ignored") end args[:override] = val[3] result = ast AST::Collection, args result end .,., module_eval(<<'.,.,', 'grammar.ra', 217) def _reduce_77(val, _values, result) type = val[0].value.downcase args = {:type => type } if val[1].is_a?(AST::CollExpr) args[:query] = val[1] args[:query].type = type args[:form] = args[:query].form else args[:form] = val[1] end if args[:form] == :exported and ! Puppet[:storeconfigs] Puppet.warning addcontext("You cannot collect exported resources without storeconfigs being set; the collection will be ignored") end result = ast AST::Collection, args result end .,., module_eval(<<'.,.,', 'grammar.ra', 235) def _reduce_78(val, _values, result) if val[1] result = val[1] result.form = :virtual else result = :virtual end result end .,., module_eval(<<'.,.,', 'grammar.ra', 243) def _reduce_79(val, _values, result) if val[1] result = val[1] result.form = :exported else result = :exported end result end .,., # reduce 80 omitted # reduce 81 omitted module_eval(<<'.,.,', 'grammar.ra', 256) def _reduce_82(val, _values, result) result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] result end .,., # reduce 83 omitted module_eval(<<'.,.,', 'grammar.ra', 261) def _reduce_84(val, _values, result) result = val[1] result.parens = true result end .,., module_eval(<<'.,.,', 'grammar.ra', 265) def _reduce_85(val, _values, result) result=val[0][:value] result end .,., module_eval(<<'.,.,', 'grammar.ra', 266) def _reduce_86(val, _values, result) result=val[0][:value] result end .,., module_eval(<<'.,.,', 'grammar.ra', 269) def _reduce_87(val, _values, result) result = ast AST::CollExpr, :test1 => val[0], :oper => val[1][:value], :test2 => val[2] #result = ast AST::CollExpr #result.push *val result end .,., module_eval(<<'.,.,', 'grammar.ra', 274) def _reduce_88(val, _values, result) result = ast AST::CollExpr, :test1 => val[0], :oper => val[1][:value], :test2 => val[2] #result = ast AST::CollExpr #result.push *val result end .,., # reduce 89 omitted # reduce 90 omitted module_eval(<<'.,.,', 'grammar.ra', 283) def _reduce_91(val, _values, result) result = ast AST::ResourceInstance, :title => val[0], :parameters => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 286) def _reduce_92(val, _values, result) result = aryfy(val[0]) result end .,., module_eval(<<'.,.,', 'grammar.ra', 288) def _reduce_93(val, _values, result) val[0].push val[2] result = val[0] result end .,., # reduce 94 omitted # reduce 95 omitted module_eval(<<'.,.,', 'grammar.ra', 296) def _reduce_96(val, _values, result) result = ast AST::Undef, :value => :undef result end .,., module_eval(<<'.,.,', 'grammar.ra', 300) def _reduce_97(val, _values, result) result = ast AST::Name, :value => val[0][:value], :line => val[0][:line] result end .,., module_eval(<<'.,.,', 'grammar.ra', 304) def _reduce_98(val, _values, result) result = ast AST::Type, :value => val[0][:value], :line => val[0][:line] result end .,., # reduce 99 omitted # reduce 100 omitted # reduce 101 omitted # reduce 102 omitted # reduce 103 omitted # reduce 104 omitted # reduce 105 omitted module_eval(<<'.,.,', 'grammar.ra', 316) def _reduce_106(val, _values, result) raise Puppet::ParseError, "Cannot assign to variables in other namespaces" if val[0][:value] =~ /::/ # this is distinct from referencing a variable variable = ast AST::Name, :value => val[0][:value], :line => val[0][:line] result = ast AST::VarDef, :name => variable, :value => val[2], :line => val[0][:line] result end .,., module_eval(<<'.,.,', 'grammar.ra', 322) def _reduce_107(val, _values, result) result = ast AST::VarDef, :name => val[0], :value => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 326) def _reduce_108(val, _values, result) variable = ast AST::Name, :value => val[0][:value], :line => val[0][:line] result = ast AST::VarDef, :name => variable, :value => val[2], :append => true, :line => val[0][:line] result end .,., module_eval(<<'.,.,', 'grammar.ra', 332) def _reduce_109(val, _values, result) result = ast AST::ASTArray result end .,., module_eval(<<'.,.,', 'grammar.ra', 334) def _reduce_110(val, _values, result) result = aryfy(val[0]) result end .,., module_eval(<<'.,.,', 'grammar.ra', 336) def _reduce_111(val, _values, result) val[0].push(val[2]) result = val[0] result end .,., # reduce 112 omitted # reduce 113 omitted # reduce 114 omitted module_eval(<<'.,.,', 'grammar.ra', 345) def _reduce_115(val, _values, result) result = ast AST::ResourceParam, :param => val[0][:value], :line => val[0][:line], :value => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 349) def _reduce_116(val, _values, result) result = ast AST::ResourceParam, :param => val[0][:value], :line => val[0][:line], :value => val[2], :add => true result end .,., # reduce 117 omitted # reduce 118 omitted module_eval(<<'.,.,', 'grammar.ra', 358) def _reduce_119(val, _values, result) result = ast AST::ASTArray result end .,., module_eval(<<'.,.,', 'grammar.ra', 360) def _reduce_120(val, _values, result) result = aryfy(val[0]) result end .,., module_eval(<<'.,.,', 'grammar.ra', 362) def _reduce_121(val, _values, result) val[0].push(val[2]) result = val[0] result end .,., module_eval(<<'.,.,', 'grammar.ra', 368) def _reduce_122(val, _values, result) result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => val[2], :ftype => :rvalue result end .,., module_eval(<<'.,.,', 'grammar.ra', 373) def _reduce_123(val, _values, result) result = ast AST::Function, :name => val[0][:value], :line => val[0][:line], :arguments => AST::ASTArray.new({}), :ftype => :rvalue result end .,., module_eval(<<'.,.,', 'grammar.ra', 379) def _reduce_124(val, _values, result) result = ast AST::String, :value => val[0][:value], :line => val[0][:line] result end .,., module_eval(<<'.,.,', 'grammar.ra', 380) def _reduce_125(val, _values, result) result = ast AST::Concat, :value => [ast(AST::String,val[0])]+val[1], :line => val[0][:line] result end .,., module_eval(<<'.,.,', 'grammar.ra', 382) def _reduce_126(val, _values, result) result = [val[0]] + val[1] result end .,., module_eval(<<'.,.,', 'grammar.ra', 384) def _reduce_127(val, _values, result) result = [ast(AST::String,val[0])] result end .,., module_eval(<<'.,.,', 'grammar.ra', 385) def _reduce_128(val, _values, result) result = [ast(AST::String,val[0])] + val[1] result end .,., module_eval(<<'.,.,', 'grammar.ra', 388) def _reduce_129(val, _values, result) result = ast AST::Boolean, :value => val[0][:value], :line => val[0][:line] result end .,., module_eval(<<'.,.,', 'grammar.ra', 392) def _reduce_130(val, _values, result) Puppet.warning addcontext("Deprecation notice: Resource references should now be capitalized") result = ast AST::ResourceReference, :type => val[0][:value], :line => val[0][:line], :title => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 395) def _reduce_131(val, _values, result) result = ast AST::ResourceReference, :type => val[0].value, :title => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 399) def _reduce_132(val, _values, result) @lexer.commentpop args = { :test => ast(AST::Not, :value => val[1]), :statements => val[3] } result = ast AST::IfStatement, args result end .,., module_eval(<<'.,.,', 'grammar.ra', 408) def _reduce_133(val, _values, result) @lexer.commentpop args = { :test => ast(AST::Not, :value => val[1]), :statements => ast(AST::Nop) } result = ast AST::IfStatement, args result end .,., module_eval(<<'.,.,', 'grammar.ra', 417) def _reduce_134(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'grammar.ra', 421) def _reduce_135(val, _values, result) @lexer.commentpop args = { :test => val[0], :statements => val[2] } args[:else] = val[4] if val[4] result = ast AST::IfStatement, args result end .,., module_eval(<<'.,.,', 'grammar.ra', 432) def _reduce_136(val, _values, result) @lexer.commentpop args = { :test => val[0], :statements => ast(AST::Nop) } args[:else] = val[3] if val[3] result = ast AST::IfStatement, args result end .,., # reduce 137 omitted module_eval(<<'.,.,', 'grammar.ra', 445) def _reduce_138(val, _values, result) result = ast AST::Else, :statements => val[1] result end .,., module_eval(<<'.,.,', 'grammar.ra', 448) def _reduce_139(val, _values, result) @lexer.commentpop result = ast AST::Else, :statements => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 452) def _reduce_140(val, _values, result) @lexer.commentpop result = ast AST::Else, :statements => ast(AST::Nop) result end .,., # reduce 141 omitted # reduce 142 omitted module_eval(<<'.,.,', 'grammar.ra', 471) def _reduce_143(val, _values, result) result = ast AST::InOperator, :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 474) def _reduce_144(val, _values, result) result = ast AST::MatchOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 477) def _reduce_145(val, _values, result) result = ast AST::MatchOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 480) def _reduce_146(val, _values, result) result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 483) def _reduce_147(val, _values, result) result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 486) def _reduce_148(val, _values, result) result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 489) def _reduce_149(val, _values, result) result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 492) def _reduce_150(val, _values, result) result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 495) def _reduce_151(val, _values, result) result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 498) def _reduce_152(val, _values, result) result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 501) def _reduce_153(val, _values, result) result = ast AST::Minus, :value => val[1] result end .,., module_eval(<<'.,.,', 'grammar.ra', 504) def _reduce_154(val, _values, result) result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 507) def _reduce_155(val, _values, result) result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 510) def _reduce_156(val, _values, result) result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 513) def _reduce_157(val, _values, result) result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 516) def _reduce_158(val, _values, result) result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 519) def _reduce_159(val, _values, result) result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 522) def _reduce_160(val, _values, result) result = ast AST::Not, :value => val[1] result end .,., module_eval(<<'.,.,', 'grammar.ra', 525) def _reduce_161(val, _values, result) result = ast AST::BooleanOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 528) def _reduce_162(val, _values, result) result = ast AST::BooleanOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 531) def _reduce_163(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'grammar.ra', 535) def _reduce_164(val, _values, result) @lexer.commentpop result = ast AST::CaseStatement, :test => val[1], :options => val[3] result end .,., module_eval(<<'.,.,', 'grammar.ra', 539) def _reduce_165(val, _values, result) result = aryfy(val[0]) result end .,., module_eval(<<'.,.,', 'grammar.ra', 541) def _reduce_166(val, _values, result) val[0].push val[1] result = val[0] result end .,., module_eval(<<'.,.,', 'grammar.ra', 546) def _reduce_167(val, _values, result) @lexer.commentpop result = ast AST::CaseOpt, :value => val[0], :statements => val[3] result end .,., module_eval(<<'.,.,', 'grammar.ra', 549) def _reduce_168(val, _values, result) @lexer.commentpop result = ast( AST::CaseOpt, :value => val[0], :statements => ast(AST::BlockExpression) ) result end .,., module_eval(<<'.,.,', 'grammar.ra', 559) def _reduce_169(val, _values, result) result = aryfy(val[0]) result end .,., module_eval(<<'.,.,', 'grammar.ra', 561) def _reduce_170(val, _values, result) val[0].push(val[2]) result = val[0] result end .,., module_eval(<<'.,.,', 'grammar.ra', 566) def _reduce_171(val, _values, result) result = ast AST::Selector, :param => val[0], :values => val[2] result end .,., # reduce 172 omitted module_eval(<<'.,.,', 'grammar.ra', 571) def _reduce_173(val, _values, result) @lexer.commentpop result = val[1] result end .,., # reduce 174 omitted module_eval(<<'.,.,', 'grammar.ra', 577) def _reduce_175(val, _values, result) if val[0].instance_of?(AST::ASTArray) val[0].push(val[2]) result = val[0] else result = ast AST::ASTArray, :children => [val[0],val[2]] end result end .,., module_eval(<<'.,.,', 'grammar.ra', 586) def _reduce_176(val, _values, result) result = ast AST::ResourceParam, :param => val[0], :value => val[2] result end .,., # reduce 177 omitted # reduce 178 omitted # reduce 179 omitted # reduce 180 omitted # reduce 181 omitted # reduce 182 omitted # reduce 183 omitted # reduce 184 omitted module_eval(<<'.,.,', 'grammar.ra', 598) def _reduce_185(val, _values, result) result = ast AST::Default, :value => val[0][:value], :line => val[0][:line] result end .,., # reduce 186 omitted module_eval(<<'.,.,', 'grammar.ra', 603) def _reduce_187(val, _values, result) result = [val[0][:value]] result end .,., # reduce 188 omitted module_eval(<<'.,.,', 'grammar.ra', 605) def _reduce_189(val, _values, result) result = val[0] += val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 608) def _reduce_190(val, _values, result) val[1].each do |file| import(file) end result = nil result end .,., module_eval(<<'.,.,', 'grammar.ra', 618) def _reduce_191(val, _values, result) @lexer.commentpop result = Puppet::Parser::AST::Definition.new(classname(val[1]), ast_context(true).merge(:arguments => val[2], :code => val[4], :line => val[0][:line])) @lexer.indefine = false #} | DEFINE NAME argumentlist parent LBRACE RBRACE { result end .,., module_eval(<<'.,.,', 'grammar.ra', 626) def _reduce_192(val, _values, result) @lexer.commentpop result = Puppet::Parser::AST::Definition.new(classname(val[1]), ast_context(true).merge(:arguments => val[2], :line => val[0][:line])) @lexer.indefine = false result end .,., module_eval(<<'.,.,', 'grammar.ra', 634) def _reduce_193(val, _values, result) @lexer.commentpop # Our class gets defined in the parent namespace, not our own. @lexer.namepop result = Puppet::Parser::AST::Hostclass.new(classname(val[1]), ast_context(true).merge(:arguments => val[2], :parent => val[3], :code => val[5], :line => val[0][:line])) result end .,., module_eval(<<'.,.,', 'grammar.ra', 641) def _reduce_194(val, _values, result) @lexer.commentpop # Our class gets defined in the parent namespace, not our own. @lexer.namepop result = Puppet::Parser::AST::Hostclass.new(classname(val[1]), ast_context(true).merge(:arguments => val[2], :parent => val[3], :line => val[0][:line])) result end .,., module_eval(<<'.,.,', 'grammar.ra', 650) def _reduce_195(val, _values, result) @lexer.commentpop result = Puppet::Parser::AST::Node.new(val[1], ast_context(true).merge(:parent => val[2], :code => val[4], :line => val[0][:line])) result end .,., module_eval(<<'.,.,', 'grammar.ra', 655) def _reduce_196(val, _values, result) @lexer.commentpop result = Puppet::Parser::AST::Node.new(val[1], ast_context(true).merge(:parent => val[2], :line => val[0][:line])) result end .,., module_eval(<<'.,.,', 'grammar.ra', 659) def _reduce_197(val, _values, result) result = val[0][:value] result end .,., module_eval(<<'.,.,', 'grammar.ra', 660) def _reduce_198(val, _values, result) result = "class" result end .,., module_eval(<<'.,.,', 'grammar.ra', 665) def _reduce_199(val, _values, result) result = [result] result end .,., module_eval(<<'.,.,', 'grammar.ra', 668) def _reduce_200(val, _values, result) result = val[0] result << val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 673) def _reduce_201(val, _values, result) result = ast AST::HostName, :value => val[0] result end .,., module_eval(<<'.,.,', 'grammar.ra', 676) def _reduce_202(val, _values, result) result = val[0][:value] result end .,., module_eval(<<'.,.,', 'grammar.ra', 677) def _reduce_203(val, _values, result) result = val[0][:value] result end .,., module_eval(<<'.,.,', 'grammar.ra', 678) def _reduce_204(val, _values, result) result = val[0][:value] result end .,., # reduce 205 omitted module_eval(<<'.,.,', 'grammar.ra', 682) def _reduce_206(val, _values, result) result = nil result end .,., module_eval(<<'.,.,', 'grammar.ra', 686) def _reduce_207(val, _values, result) result = ast AST::ASTArray, :children => [] result end .,., # reduce 208 omitted module_eval(<<'.,.,', 'grammar.ra', 691) def _reduce_209(val, _values, result) result = nil result end .,., module_eval(<<'.,.,', 'grammar.ra', 694) def _reduce_210(val, _values, result) result = val[1] result = [result] unless result[0].is_a?(Array) result end .,., # reduce 211 omitted module_eval(<<'.,.,', 'grammar.ra', 700) def _reduce_212(val, _values, result) result = val[0] result = [result] unless result[0].is_a?(Array) result << val[2] result end .,., module_eval(<<'.,.,', 'grammar.ra', 706) def _reduce_213(val, _values, result) result = [val[0][:value], val[2]] result end .,., module_eval(<<'.,.,', 'grammar.ra', 707) def _reduce_214(val, _values, result) result = [val[0][:value]] result end .,., # reduce 215 omitted module_eval(<<'.,.,', 'grammar.ra', 711) def _reduce_216(val, _values, result) result = val[1] result end .,., # reduce 217 omitted module_eval(<<'.,.,', 'grammar.ra', 716) def _reduce_218(val, _values, result) result = val[1] result end .,., # reduce 219 omitted # reduce 220 omitted module_eval(<<'.,.,', 'grammar.ra', 722) def _reduce_221(val, _values, result) result = ast AST::Variable, :value => val[0][:value], :line => val[0][:line] result end .,., module_eval(<<'.,.,', 'grammar.ra', 725) def _reduce_222(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'grammar.ra', 726) def _reduce_223(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'grammar.ra', 727) def _reduce_224(val, _values, result) result = ast AST::ASTArray result end .,., # reduce 225 omitted # reduce 226 omitted # reduce 227 omitted module_eval(<<'.,.,', 'grammar.ra', 733) def _reduce_228(val, _values, result) result = nil result end .,., module_eval(<<'.,.,', 'grammar.ra', 736) def _reduce_229(val, _values, result) result = ast AST::Regex, :value => val[0][:value] result end .,., module_eval(<<'.,.,', 'grammar.ra', 740) def _reduce_230(val, _values, result) @lexer.commentpop if val[1].instance_of?(AST::ASTHash) result = val[1] else result = ast AST::ASTHash, { :value => val[1] } end result end .,., module_eval(<<'.,.,', 'grammar.ra', 748) def _reduce_231(val, _values, result) @lexer.commentpop if val[1].instance_of?(AST::ASTHash) result = val[1] else result = ast AST::ASTHash, { :value => val[1] } end result end .,., module_eval(<<'.,.,', 'grammar.ra', 755) def _reduce_232(val, _values, result) @lexer.commentpop result = ast AST::ASTHash result end .,., # reduce 233 omitted module_eval(<<'.,.,', 'grammar.ra', 761) def _reduce_234(val, _values, result) if val[0].instance_of?(AST::ASTHash) result = val[0].merge(val[2]) else result = ast AST::ASTHash, :value => val[0] result.merge(val[2]) end result end .,., module_eval(<<'.,.,', 'grammar.ra', 770) def _reduce_235(val, _values, result) result = ast AST::ASTHash, { :value => { val[0] => val[2] } } result end .,., module_eval(<<'.,.,', 'grammar.ra', 773) def _reduce_236(val, _values, result) result = val[0][:value] result end .,., module_eval(<<'.,.,', 'grammar.ra', 774) def _reduce_237(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'grammar.ra', 777) def _reduce_238(val, _values, result) result = ast AST::HashOrArrayAccess, :variable => val[0][:value], :key => val[2] result end .,., # reduce 239 omitted module_eval(<<'.,.,', 'grammar.ra', 782) def _reduce_240(val, _values, result) result = ast AST::HashOrArrayAccess, :variable => val[0], :key => val[2] result end .,., def _reduce_none(val, _values, result) val[0] end end # class Parser end # module Parser end # module Puppet �����������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/parser_factory.rb����������������������������������������������������0000664�0052762�0001160�00000005474�12650174557�021713� 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(environment) if Puppet.future_parser? evaluating_parser(environment) else classic_parser(environment) end end # Creates an instance of the classic parser. # def self.classic_parser(environment) # avoid expensive require if already loaded require 'puppet/parser' unless defined? Puppet::Parser::Parser Puppet::Parser::Parser.new(environment) end # Creates an instance of an E4ParserAdapter that adapts an # EvaluatingParser to the 3x way of parsing. # def self.evaluating_parser(file_watcher) # Since RGen is optional, test that it is installed assert_rgen_installed() unless defined?(Puppet::Pops::Parser::E4ParserAdapter) require 'puppet/parser/e4_parser_adapter' require 'puppet/pops/parser/code_merger' end E4ParserAdapter.new(file_watcher) end # Asserts that RGen >= 0.6.6 is installed by checking that certain behavior is available. # Note that this assert is expensive as it also requires puppet/pops (if not already loaded). # def self.assert_rgen_installed @@asserted ||= false return if @@asserted @@asserted = true begin require 'rgen/metamodel_builder' rescue LoadError raise Puppet::DevError.new("The gem 'rgen' version >= 0.7.0 is required when using the setting '--parser future'. Please install 'rgen'.") end # Since RGen is optional, there is nothing specifying its version. # It is not installed in any controlled way, so not possible to use gems to check (it may be installed some other way). # Instead check that "eContainer, and eContainingFeature" has been installed. require 'puppet/pops' begin litstring = Puppet::Pops::Model::LiteralString.new(); container = Puppet::Pops::Model::ArithmeticExpression.new(); container.left_expr = litstring raise "no eContainer" if litstring.eContainer() != container raise "no eContainingFeature" if litstring.eContainingFeature() != :left_expr rescue => e # TODO: RGen can raise exceptions for other reasons! raise Puppet::DevError.new("The gem 'rgen' version >= 0.7.0 is required when using '--parser future'. An older version is installed, please update.") end end def self.code_merger if Puppet.future_parser? Puppet::Pops::Parser::CodeMerger.new else Puppet::Parser::CodeMerger.new end end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/parser_support.rb����������������������������������������������������0000664�0052762�0001160�00000012142�12650174557�021746� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# I pulled this into a separate file, because I got # tired of rebuilding the parser.rb file all the time. require 'forwardable' class Puppet::Parser::Parser extend Forwardable require 'puppet/parser/functions' require 'puppet/parser/files' require 'puppet/resource/type_collection' require 'puppet/resource/type_collection_helper' require 'puppet/resource/type' require 'monitor' AST = Puppet::Parser::AST include Puppet::Resource::TypeCollectionHelper attr_reader :version, :environment attr_accessor :files attr_accessor :lexer # Add context to a message; useful for error messages and such. def addcontext(message, obj = nil) obj ||= @lexer message += " on line #{obj.line}" if file = obj.file message += " in file #{file}" end message end # Create an AST array containing a single element def aryfy(arg) ast AST::ASTArray, :children => [arg] end # Create an AST block containing a single element def block(arg) ast AST::BlockExpression, :children => [arg] end # Create an AST object, and automatically add the file and line information if # available. def ast(klass, hash = {}) klass.new ast_context(klass.use_docs, hash[:line]).merge(hash) end def ast_context(include_docs = false, ast_line = nil) result = { :line => ast_line || lexer.line, :file => lexer.file } result[:doc] = lexer.getcomment(result[:line]) if include_docs result end # The fully qualifed name, with the full namespace. def classname(name) [@lexer.namespace, name].join("::").sub(/^::/, '') end def clear initvars end # Raise a Parse error. def error(message, options = {}) if @lexer.expected message += "; expected '%s'" end except = Puppet::ParseError.new(message) except.line = options[:line] || @lexer.line except.file = options[:file] || @lexer.file raise except end def_delegators :@lexer, :file, :string= def file=(file) unless Puppet::FileSystem.exist?(file) unless file =~ /\.pp$/ file = file + ".pp" end end raise Puppet::AlreadyImportedError, "Import loop detected for #{file}" if known_resource_types.watching_file?(file) watch_file(file) @lexer.file = file end def_delegators :known_resource_types, :hostclass, :definition, :node, :nodes? def_delegators :known_resource_types, :find_hostclass, :find_definition def_delegators :known_resource_types, :watch_file, :version def import(file) deprecation_location_text = if @lexer.file && @lexer.line " at #{@lexer.file}:#{@lexer.line}" elsif @lexer.file " in file #{@lexer.file}" elsif @lexer.line " at #{@lexer.line}" end Puppet.deprecation_warning("The use of 'import' is deprecated#{deprecation_location_text}. See http://links.puppetlabs.com/puppet-import-deprecation") if @lexer.file # use a path relative to the file doing the importing dir = File.dirname(@lexer.file) else # otherwise assume that everything needs to be from where the user is # executing this command. Normally, this would be in a "puppet apply -e" dir = Dir.pwd end known_resource_types.loader.import(file, dir) end def initialize(env) @environment = env initvars end # Initialize or reset all of our variables. def initvars @lexer = Puppet::Parser::Lexer.new end # Split an fq name into a namespace and name def namesplit(fullname) ary = fullname.split("::") n = ary.pop || "" ns = ary.join("::") return ns, n end def on_error(token,value,stack) if token == 0 # denotes end of file value = 'end of file' else value = "'#{value[:value]}'" end error = "Syntax error at #{value}" if brace = @lexer.expected error += "; expected '#{brace}'" end except = Puppet::ParseError.new(error) except.line = @lexer.line except.file = @lexer.file if @lexer.file raise except end # how should I do error handling here? def parse(string = nil) if self.file =~ /\.rb$/ main = parse_ruby_file else self.string = string if string begin @yydebug = false main = yyparse(@lexer,:scan) 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, nil, except) end end # Store the results as the top-level class. return Puppet::Parser::AST::Hostclass.new('', :code => main) ensure @lexer.clear end def parse_ruby_file Puppet.deprecation_warning("Use of the Ruby DSL is deprecated.") # Execute the contents of the file inside its own "main" object so # that it can call methods in the resource type API. main_object = Puppet::DSL::ResourceTypeAPI.new main_object.instance_eval(File.read(self.file)) # Then extract any types that were created. Puppet::Parser::AST::BlockExpression.new :children => main_object.instance_eval { @__created_ast_objects__ } end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/relationship.rb������������������������������������������������������0000664�0052762�0001160�00000005077�12650174557�021370� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Parser::Relationship attr_accessor :source, :target, :type PARAM_MAP = {:relationship => :before, :subscription => :notify} def arrayify(resources) # This if statement is needed because the 3x parser cannot load # Puppet::Pops. This logic can be removed for 4.0 when the 3x AST # is removed (when Pops is always used). if !(Puppet.future_parser?) case resources when Puppet::Parser::Collector resources.collected.values when Array resources else [resources] end else require 'puppet/pops' case resources when Puppet::Pops::Evaluator::Collectors::AbstractCollector resources.collected.values when Array resources else [resources] end end end def evaluate(catalog) arrayify(source).each do |s| arrayify(target).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 #{type}") end def mk_relationship(source, target, catalog) # REVISIT: In Ruby 1.8 we applied `to_s` to source and target, rather than # `join` without an argument. In 1.9 the behaviour of Array#to_s changed, # and it gives a different representation than just concat the stringified # elements. # # This restores the behaviour, but doesn't address the underlying question # of what would happen when more than one item was passed in that array. # (Hint: this will not end well.) # # See http://projects.puppetlabs.com/issues/12076 for the ticket tracking # the fact that we should dig out the sane version of this behaviour, then # implement it - where we don't risk breaking a stable release series. # --daniel 2012-01-21 source = source.is_a?(Array) ? source.join : source.to_s target = target.is_a?(Array) ? target.join : target.to_s unless source_resource = catalog.resource(source) raise ArgumentError, "Could not find resource '#{source}' for relationship on '#{target}'" end unless catalog.resource(target) raise ArgumentError, "Could not find resource '#{target}' for relationship from '#{source}'" end Puppet.debug "Adding relationship from #{source} to #{target} with '#{param_name}'" if source_resource[param_name].class != Array source_resource[param_name] = [source_resource[param_name]].compact end source_resource[param_name] << target end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/resource.rb����������������������������������������������������������0000664�0052762�0001160�00000021003�12650174557�020501� 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' require 'puppet/parser/yaml_trimmer' require 'puppet/resource/type_collection_helper' include Puppet::Resource::TypeCollectionHelper include Puppet::Util include Puppet::Util::MethodHelper include Puppet::Util::Errors include Puppet::Util::Logging include Puppet::Parser::YamlTrimmer 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 #{self[:stage] || :main} specified by #{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 #{self}", [:compiler, :evaluate_resource, self]) do @evaluated = true if klass = resource_type and ! builtin_type? finish evaluated_code = klass.evaluate_code(self) return evaluated_code elsif builtin? devfail "Cannot evaluate a builtin type (#{type})" else self.fail "Cannot find definition #{type}" 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 # Do any finishing work on this object, called before evaluation or # before storage/translation. def finish return if finished? @finished = true add_defaults add_scope_tags validate end # Has this resource already been finished? def finished? @finished end def initialize(*args) raise ArgumentError, "Resources require a hash as last argument" unless args.last.is_a? Hash raise ArgumentError, "Resources require a scope" unless args.last[:scope] super @source ||= scope.source end # Is this resource modeling an isomorphic resource type? def isomorphic? if builtin_type? return resource_type.isomorphic? else return true end 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.line, resource.file) 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 # This only mattered for clients < 0.25, which we don't support any longer. # ...but, since this hasn't been deprecated, and at least some functions # used it, deprecate now rather than just eliminate. --daniel 2012-07-15 def metaparam_compatibility_mode? Puppet.deprecation_warning "metaparam_compatibility_mode? is obsolete since < 0.25 clients are really, really not supported any more" false 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 = 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 @parameters.inject({}) do |hash, ary| param = ary[1] # Skip "undef" and nil values. hash[param.name] = param.value if param.value != :undef && !param.value.nil? hash 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 private # Add default values from our definition. def add_defaults scope.lookupdefaults(self.type).each do |name, param| unless @parameters.include?(name) self.debug "Adding default for #{name}" @parameters[name] = param.dup end end end def add_scope_tags if scope_resource = scope.resource tag(*scope_resource.tags) 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] # The parameter is already set. Fail if they're not allowed to override it. unless param.source.child_of?(current.source) msg = "Parameter '#{param.name}' is already set on #{self}" msg += " by #{current.source}" if current.source.to_s != "" if current.file or current.line fields = [] fields << current.file if current.file fields << current.line.to_s if current.line msg += " at #{fields.join(":")}" end msg += "; cannot redefine" raise Puppet::ParseError.new(msg, param.line, param.file) 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 # implcitly shared when multiple resources are overrriden 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 @parameters.each do |name, param| validate_parameter(name) end rescue => detail self.fail Puppet::ParseError, detail.to_s + " on #{self}", detail end def extract_parameters(params) params.each do |param| # Don't set the same parameter twice self.fail Puppet::ParseError, "Duplicate parameter '#{param.name}' for on #{self}" if @parameters[param.name] set_parameter(param) end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/resource/������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020157� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/parser/resource/param.rb����������������������������������������������������0000664�0052762�0001160�00000001006�12650174557�021602� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/yaml_trimmer' # The parameters we stick in Resources. class Puppet::Parser::Resource::Param include Puppet::Util include Puppet::Util::Errors include Puppet::Util::MethodHelper include Puppet::Parser::YamlTrimmer 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-3.8.5/lib/puppet/parser/scope.rb�������������������������������������������������������������0000664�0052762�0001160�00000070144�12650174557�017775� 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/resource/type_collection_helper' 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 include Puppet::Resource::TypeCollectionHelper require 'puppet/parser/resource' AST = Puppet::Parser::AST # Variables that always exist with nil value even if not set BUILT_IN_VARS = ['module_name'.freeze, 'caller_module_name'.freeze].freeze Puppet::Util.logmethods(self) include Puppet::Util::Errors attr_accessor :source, :resource attr_accessor :compiler attr_accessor :parent attr_reader :namespaces # Hash of hashes of default values per type name attr_reader :defaults # Add some alias methods that forward to the compiler, since we reference # them frequently enough to justify the extra method call. def_delegators :compiler, :catalog, :environment # 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) if @symbols.include?(name) @symbols[name] else super end 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}" end def delete(name) # TODO: Bad choice of exception raise Puppet::ParseError, "Numerical variables cannot be deleted: Attempt to delete: $#{name}" end def add_entries_to(target = {}) # do not include match data ($0-$n) super 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) ! self[name].nil? 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 # Add to our list of namespaces. def add_namespace(ns) return false if @namespaces.include?(ns) if @namespaces == [""] @namespaces = [ns] else @namespaces << ns end end def find_hostclass(name, options = {}) known_resource_types.find_hostclass(namespaces, name, options) end def find_definition(name) known_resource_types.find_definition(namespaces, 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 # This just delegates directly. def_delegator :compiler, :findresource # Initialize our new scope. Defaults to having no parent. def initialize(compiler, options = {}) if compiler.is_a? Puppet::Parser::Compiler self.compiler = compiler else raise Puppet::DevError, "you must pass a compiler instance to a new scope object" end if n = options.delete(:namespace) @namespaces = [n] else @namespaces = [""] end raise Puppet::DevError, "compiler passed in options" if options.include? :compiler 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 = {} @enable_immutable_data = Puppet[:immutable_node_data] 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 # Look up a defined type. def lookuptype(name) find_definition(name) || 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 # # @return Object the value of the variable, or nil if it's not found # # @api public def lookupvar(name, options = {}) unless name.is_a? String raise Puppet::ParseError, "Scope variable name #{name.inspect} is a #{name.class}, not a string" end table = @ephemeral.last if name =~ /^(.*)::(.+)$/ class_name = $1 variable_name = $2 lookup_qualified_variable(class_name, variable_name, options) # TODO: optimize with an assoc instead, this searches through scopes twice for a hit elsif table.include?(name) table[name] else next_scope = inherited_scope || enclosing_scope if next_scope next_scope.lookupvar(name, options) else variable_not_found(name) end end end def variable_not_found(name, reason=nil) # Built in variables always exist if BUILT_IN_VARS.include?(name) return nil end if Puppet[:strict_variables] if Puppet.future_parser? throw :undefined_variable else reason_msg = reason.nil? ? '' : "; #{reason}" raise Puppet::ParseError, "Undefined variable #{name.inspect}#{reason_msg}" end else nil end 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={}) lookupvar(varname, options) end # The scope of the inherited thing of this scope's resource. This could # either be a node that was inherited or the class. # # @return [Puppet::Parser::Scope] The scope or nil if there is not an inherited scope def inherited_scope if has_inherited_class? 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? or parent.is_nodescope? parent else parent.enclosing_scope end else nil end end def is_classscope? resource and resource.type == "Class" end def is_nodescope? resource and resource.type == "Node" end def is_topscope? compiler and self == compiler.topscope end def lookup_qualified_variable(class_name, variable_name, position) begin if lookup_as_local_name?(class_name, variable_name) if is_topscope? # This is the case where $::x is looked up from within the topscope itself, or from a local scope # parented at the top scope. In this case, the lookup must ignore local and ephemeral scopes. # handle_not_found(class_name, variable_name, position) unless @symtable.include?(variable_name) @symtable[variable_name] else self[variable_name] end else qualified_scope(class_name).lookupvar(variable_name, position) end rescue RuntimeError => e handle_not_found(class_name, variable_name, position, e.message) end 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] " at #{position[:lineproc].call}" elsif position[:file] && position[:line] " at #{position[:file]}:#{position[:line]}" else "" end warning "Could not look up qualified variable '#{class_name}::#{variable_name}'; #{reason}#{location}" end variable_not_found("#{class_name}::#{variable_name}", reason) end # Handles the special case of looking up fully qualified variable in not yet evaluated top scope # This is ok if the lookup request originated in topscope (this happens when evaluating # bindings; using the top scope to provide the values for facts. # @param class_name [String] the classname part of a variable name, may be special "" # @param variable_name [String] the variable name without the absolute leading '::' # @return [Boolean] true if the given variable name should be looked up directly in this scope # def lookup_as_local_name?(class_name, variable_name) # not a local if name has more than one segment return nil if variable_name =~ /::/ # partial only if the class for "" cannot be found return nil unless class_name == "" && klass = find_hostclass(class_name) && class_scope(klass).nil? is_topscope? end def has_inherited_class? is_classscope? and resource.resource_type.parent end private :has_inherited_class? def has_enclosing_scope? not parent.nil? end private :has_enclosing_scope? def qualified_scope(classname) raise "class #{classname} could not be found" unless klass = find_hostclass(classname) raise "class #{classname} has not been evaluated" 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. # # This is currently a wrapper for to_hash_legacy or to_hash_future. # # @see to_hash_future # # @see to_hash_legacy def to_hash(recursive = true) @future_parser ||= Puppet.future_parser? if @future_parser to_hash_future(recursive) else to_hash_legacy(recursive) end end # Fixed version of to_hash that implements scoping correctly (i.e., with # dynamic scoping disabled #28200 / PUP-1220 # # @see to_hash def to_hash_future(recursive) if recursive and has_enclosing_scope? target = enclosing_scope.to_hash_future(recursive) if !(inherited = inherited_scope).nil? target.merge!(inherited.to_hash_future(recursive)) end else target = Hash.new end # add all local scopes @ephemeral.last.add_entries_to(target) target end # The old broken implementation of to_hash that retains the dynamic scoping # semantics # # @see to_hash def to_hash_legacy(recursive = true) if recursive and parent target = parent.to_hash_legacy(recursive) else target = Hash.new end # add all local scopes @ephemeral.last.add_entries_to(target) target end def namespaces @namespaces.dup 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 return nil unless @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.name} }; cannot redefine", param.line, param.file) end table[param.name] = param } end RESERVED_VARIABLE_NAMES = ['trusted', 'facts'].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 = {}) if name =~ /^[0-9]+$/ raise Puppet::ParseError.new("Cannot assign to a numeric match result variable '$#{name}'") # unless options[:ephemeral] end unless name.is_a? String raise Puppet::ParseError, "Scope variable name #{name.inspect} is a #{name.class}, not a string" end # Check for reserved variable names if @enable_immutable_data && !options[:privileged] && RESERVED_VARIABLE_NAMES.include?(name) raise Puppet::ParseError, "Attempt to assign to a reserved variable name: '#{name}'" end table = effective_symtable(options[:ephemeral]) if table.bound?(name) if options[:append] error = Puppet::ParseError.new("Cannot append, variable #{name} is defined in this scope") else error = Puppet::ParseError.new("Cannot reassign variable #{name}") end error.file = options[:file] if options[:file] error.line = options[:line] if options[:line] raise error end if options[:append] # produced result (value) is the resulting appended value, note: the table[]= does not return the value table[name] = (value = append_value(undef_as('', self[name]), value)) else table[name] = value end value end def set_trusted(hash) setvar('trusted', deep_freeze(hash), :privileged => true) end def set_facts(hash) # Remove _timestamp (it has an illegal datatype). It is not allowed to mutate the given hash # since it contains the facts. hash = hash.dup hash.delete('_timestamp') setvar('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: '#{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.last if use_ephemeral return s || @symtable else while s && !s.is_local_scope?() s = s.parent end s || @symtable end 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. # # @api public # def []=(varname, value, options = {}) setvar(varname, value, options = {}) end def append_value(bound_value, new_value) case new_value when Array bound_value + new_value when Hash bound_value.merge(new_value) else if bound_value.is_a?(Hash) raise ArgumentError, "Trying to append to a hash with something which is not a hash is unsupported" end bound_value + new_value end end private :append_value # Return the tags associated with this scope. def_delegator :resource, :tags # Used mainly for logging def to_s "Scope(#{@resource})" end alias_method :inspect, :to_s # remove ephemeral scope up to level # TODO: Who uses :all ? Remove ?? # def unset_ephemeral_var(level=:all) if level == :all @ephemeral = [ MatchScope.new(@symtable, nil)] else @ephemeral.pop(@ephemeral.size - level) end 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 # 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 #{match.class}") unless match.is_a?(MatchData) # Create a match ephemeral and set values from match data new_match_scope(match) end end def find_resource_type(type) # It still works fine without the type == 'class' short-cut, but it is a lot slower. return nil if ["class", "node"].include? type.to_s.downcase find_builtin_resource_type(type) || find_defined_resource_type(type) end def find_builtin_resource_type(type) Puppet::Type.type(type.to_s.downcase.to_sym) end def find_defined_resource_type(type) known_resource_types.find_definition(namespaces, type.to_s.downcase) 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!" end end def resolve_type_and_titles(type, titles) raise ArgumentError, "titles must be an array" unless titles.is_a?(Array) case type.downcase when "class" # resolve the titles titles = titles.collect do |a_title| hostclass = find_hostclass(a_title) hostclass ? hostclass.name : a_title end when "node" # no-op else # resolve the type resource_type = find_resource_type(type) type = resource_type.name if resource_type end return [type, titles] 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 # Names are now made absolute if Puppet.future_parser? is true, this will # be the default behavior in Puppet 4.0 # # Transforms Class[] and Resource[] type referenes 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). # # TODO: Change this for 4.0 to always make names absolute # # @param names [Array<String>] names to (optionally) make absolute # @return [Array<String>] names after transformation # def transform_and_assert_classnames(names) if Puppet.future_parser? names.map do |name| case name when String 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::PHostClassType 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 end else names 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) if !Puppet.future_parser?(compiler.environment) return self.send("function_#{func_name}", args, &block) else Puppet::Pops::Parser::EvaluatingParser.new.evaluator.external_call_function(func_name, args, self, &block) end end private def assert_class_and_title(type_name, title) if type_name.nil? || type_name == '' raise ArgumentError, "Cannot use an unspecific Resource[] where a Resource['class', name] is expected" end unless type_name =~ /^[Cc]lass$/ raise ArgumentError, "Cannot use a Resource[#{type_name}] where a Resource['class', name] is expected" end if title.nil? 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-3.8.5/lib/puppet/parser/templatewrapper.rb���������������������������������������������������0000664�0052762�0001160�00000007343�12650174557�022101� 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 # Ruby treats variables like methods, so we used to expose variables # within scope to the ERB code via method_missing. As per RedMine #1427, # though, this means that conflicts between methods in our inheritance # tree (Kernel#fork) and variable names (fork => "yes/no") could arise. # # Worse, /new/ conflicts could pop up when a new kernel or object method # was added to Ruby, causing templates to suddenly fail mysteriously when # Ruby was upgraded. # # To ensure that legacy templates using unqualified names work we retain # the missing_method definition here until we declare the syntax finally # dead. def method_missing(name, *args) line_number = script_line if scope.include?(name.to_s) Puppet.deprecation_warning("Variable access via '#{name}' is deprecated. Use '@#{name}' instead. #{to_s}:#{line_number}") return scope[name.to_s, { :file => @__file__, :line => line_number }] else # Just throw an error immediately, instead of searching for # other missingmethod things or whatever. raise Puppet::ParseError.new("Could not find value for '#{name}'", @__file__, line_number) end 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}'" end # We'll only ever not have a parser in testing, but, eh. scope.known_resource_types.watch_file(@__file__) 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. benchmark(:debug, "Bound template variables for #{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}") 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-3.8.5/lib/puppet/parser/type_loader.rb�������������������������������������������������������0000664�0052762�0001160�00000010731�12650174557�021167� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'find' require 'forwardable' require 'puppet/node/environment' require 'puppet/parser/parser_factory' class Puppet::Parser::TypeLoader extend Forwardable # 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) return if Puppet[:ignoreimport] 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,.rb}' : '' ) 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 Puppet::ImportError => detail # I'm not convienced 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(environment) 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 Puppet::ImportError, "No file(s) found for import of '#{pattern}'" end def load_files(modname, files) @loaded ||= {} loaded_asts = [] files.reject { |file| @loaded[file] }.each do |file| # NOTE: This ugly implementation will be replaced in Puppet 3.5. # The implementation now makes use of a global variable because the context support is # not available until Puppet 3.5. # The use case is that parsing for the purpose of searching for information # should not abort. There is currently one such use case 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 would # 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-3.8.5/lib/puppet/parser/yaml_trimmer.rb������������������������������������������������������0000664�0052762�0001160�00000000171�12650174557�021356� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Parser::YamlTrimmer REMOVE = [:@scope, :@source] def to_yaml_properties super - REMOVE end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops.rb���������������������������������������������������������������������0000664�0052762�0001160�00000010540�12650174557�016343� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module 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 require 'puppet/pops/patterns' require 'puppet/pops/utils' require 'puppet/pops/adaptable' require 'puppet/pops/adapters' require 'puppet/pops/visitable' require 'puppet/pops/visitor' require 'puppet/pops/containment' 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/model/model' # (the Types module initializes itself) require 'puppet/pops/types/types' require 'puppet/pops/types/type_calculator' require 'puppet/pops/types/type_factory' require 'puppet/pops/types/type_parser' require 'puppet/pops/types/class_loader' require 'puppet/pops/types/enumeration' module Model require 'puppet/pops/model/tree_dumper' require 'puppet/pops/model/ast_transformer' require 'puppet/pops/model/ast_tree_dumper' require 'puppet/pops/model/factory' require 'puppet/pops/model/model_tree_dumper' require 'puppet/pops/model/model_label_provider' end module Binder module SchemeHandler # the handlers are auto loaded via bindings end module Producers require 'puppet/pops/binder/producers' end require 'puppet/pops/binder/binder' require 'puppet/pops/binder/bindings_model' require 'puppet/pops/binder/binder_issues' require 'puppet/pops/binder/bindings_checker' require 'puppet/pops/binder/bindings_factory' require 'puppet/pops/binder/bindings_label_provider' require 'puppet/pops/binder/bindings_validator_factory' require 'puppet/pops/binder/injector_entry' require 'puppet/pops/binder/key_factory' require 'puppet/pops/binder/injector' require 'puppet/pops/binder/bindings_composer' require 'puppet/pops/binder/bindings_model_dumper' require 'puppet/pops/binder/system_bindings' require 'puppet/pops/binder/bindings_loader' require 'puppet/pops/binder/lookup' module Config require 'puppet/pops/binder/config/binder_config' require 'puppet/pops/binder/config/binder_config_checker' require 'puppet/pops/binder/config/issues' require 'puppet/pops/binder/config/diagnostic_producer' 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' end module Validation require 'puppet/pops/validation/checker4_0' require 'puppet/pops/validation/validator_factory_4_0' end module Evaluator require 'puppet/pops/evaluator/callable_signature' require 'puppet/pops/evaluator/runtime3_converter' require 'puppet/pops/evaluator/runtime3_support' require 'puppet/pops/evaluator/evaluator_impl' require 'puppet/pops/evaluator/epp_evaluator' require 'puppet/pops/evaluator/callable_mismatch_describer' 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 # 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 end require 'puppet/parser/ast/pops_bridge' require 'puppet/bindings' require 'puppet/functions' require 'puppet/loaders' end ����������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/�����������������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�016015� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/adaptable.rb�����������������������������������������������������������0000664�0052762�0001160�00000016750�12650174557�020271� 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::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 = :"@#{instance_var_name(self.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 = :"@#{instance_var_name(self.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 = :"@#{instance_var_name(self.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) attr_name = :"@#{instance_var_name(adapter.class.name)}" o.instance_variable_set(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 # @private # def self.instance_var_name(name) name.split("::").join('_') end end end ������������������������puppet-3.8.5/lib/puppet/pops/adapters.rb������������������������������������������������������������0000664�0052762�0001160�00000007532�12650174557�020155� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The Adapters module contains adapters for Documentation, Origin, SourcePosition, and Loader. # module Puppet::Pops::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 < Puppet::Pops::Adaptable::Adapter # @return [String] The documentation associated with an object attr_accessor :documentation end # A SourcePosAdapter holds a reference to a *Positioned* object (object that has offset and length). # This somewhat complex structure makes it possible to correctly refer to a source position # in source that is embedded in some resource; a parser only sees the embedded snippet of source text # and does not know where it was embedded. It also enables lazy evaluation of source positions (they are # rarely needed - typically just when there is an error to report. # # @note It is relatively expensive to compute line and position on line - it is not something that # should be done for every token or model object. # # @see Puppet::Pops::Utils#find_adapter, Puppet::Pops::Utils#find_closest_positioned # class SourcePosAdapter < Puppet::Pops::Adaptable::Adapter attr_accessor :locator def self.create_adapter(o) new(o) end def initialize(o) @adapted = o end def locator # The locator is always the parent locator, all positioned objects are positioned within their # parent. If a positioned object also has a locator that locator is for its children! # @locator ||= find_locator(@adapted.eContainer) end def find_locator(o) if o.nil? raise ArgumentError, "InternalError: SourcePosAdapter for something that has no locator among parents" end case when o.is_a?(Puppet::Pops::Model::Program) return o.locator # TODO_HEREDOC use case of SubLocator instead when o.is_a?(Puppet::Pops::Model::SubLocatedExpression) && !(found_locator = o.locator).nil? return found_locator when adapter = self.class.get(o) return adapter.locator else find_locator(o.eContainer) end end private :find_locator def offset @adapted.offset end def length @adapted.length end # Produces the line number for the given offset. # @note This is an expensive operation # def line # Optimization: manual inlining of locator accessor since this method is frequently called (@locator ||= find_locator(@adapted.eContainer)).line_for_offset(offset) end # Produces the position on the line of the given offset. # @note This is an expensive operation # def pos locator.pos_on_line(offset) end # Extracts the text represented by this source position (the string is obtained from the locator) def extract_text locator.string.slice(offset, length) 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 f = locator.file f = 'string:' if f.nil? || f.empty? URI("#{f}?line=#{line.to_s}&pos=#{pos.to_s}") end end # A LoaderAdapter adapts an object with a {Puppet::Pops::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 Puppet::Pops::Utils#find_adapter # class LoaderAdapter < Puppet::Pops::Adaptable::Adapter # @return [Puppet::Pops::Loader::Loader] the loader attr_accessor :loader end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/����������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017260� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/binder.rb�������������������������������������������������������0000664�0052762�0001160�00000031245�12650174557�021056� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The Binder is responsible for processing layered bindings that can be used to setup an Injector. # # An instance should be created and a call to {#define_layers} should be made which will process the layered bindings # (handle overrides, abstract entries etc.). # The constructed hash with `key => InjectorEntry` mappings is obtained as {#injector_entries}, and is used to initialize an # {Puppet::Pops::Binder::Injector Injector}. # # @api public # class Puppet::Pops::Binder::Binder # @api private attr_reader :injector_entries # @api private attr :id_index # @api private attr_reader :key_factory # A parent Binder or nil # @api private attr_reader :parent # The next anonymous key to use # @api private attr_reader :anonymous_key # This binder's precedence # @api private attr_reader :binder_precedence # @api public def initialize(layered_bindings, parent_binder=nil) @parent = parent_binder @id_index = Hash.new() { |k, v| [] } @key_factory = Puppet::Pops::Binder::KeyFactory.new() # Resulting hash of all key -> binding @injector_entries = {} if @parent.nil? @anonymous_key = 0 @binder_precedence = 0 else # First anonymous key is the parent's next (non incremented key). (The parent can not change, it is # the final, free key). @anonymous_key = @parent.anonymous_key @binder_precedence = @parent.binder_precedence + 1 end define_layers(layered_bindings) end # Binds layers from highest to lowest as defined by the given LayeredBindings. # @note # The model should have been # validated to get better error messages if the model is invalid. This implementation expects the model # to be valid, and any errors raised will be more technical runtime errors. # # @param layered_bindings [Puppet::Pops::Binder::Bindings::LayeredBindings] the named and ordered layers # @raise ArgumentError if this binder is already configured # @raise ArgumentError if bindings with unresolved 'override' surfaces as an effective binding # @raise ArgumentError if the given argument has the wrong type, or if model is invalid in some way # @return [Puppet::Pops::Binder::Binder] self # @api public # def define_layers(layered_bindings) LayerProcessor.new(self, key_factory).bind(layered_bindings) contribution_keys = [] # make one pass over entries to collect contributions, and check overrides injector_entries.each do |k,v| if key_factory.is_contributions_key?(k) contribution_keys << [k,v] elsif !v.is_resolved?() raise ArgumentError, "Binding with unresolved 'override' detected: #{self.class.format_binding(v.binding)}}" else # if binding has an id, add it to the index add_id_to_index(v.binding) end end # If a lower level binder has contributions for a key also contributed to in this binder # they must included in the higher shadowing contribution. # If a contribution is made to an id that is defined in a parent # contribute to an id that is defined in a lower binder, it must be promoted to this binder (copied) or # there is risk of making the lower level injector dirty. # contribution_keys.each do |kv| parent_contribution = lookup_in_parent(kv[0]) next unless parent_contribution injector_entries[kv[0]] = kv[1] + parent_contributions # key the multibind_id from the contribution key multibind_id = key_factory.multibind_contribution_key_to_id(kv[0]) promote_matching_bindings(self, @parent, multibind_id) end end private :define_layers # @api private def next_anonymous_key tmp = @anonymous_key @anonymous_key += 1 tmp end def add_id_to_index(binding) return unless binding.is_a?(Puppet::Pops::Binder::Bindings::Multibinding) && !(id = binding.id).nil? @id_index[id] = @id_index[id] << binding end def promote_matching_bindings(to_binder, from_binder, multibind_id) return if from_binder.nil? from_binder.id_index[ multibind_id ].each do |binding| key = key_factory.binding_key(binding) entry = lookup(key) unless entry.precedence == @binder_precedence # it is from a lower layer it must be promoted injector_entries[ key ] = Puppet::Pops::Binder::InjectorEntry.new(binding, binder_precedence) end end # recursive "up the parent chain" to promote all promote_matching_bindings(to_binder, from_binder.parent, multibind_id) end def lookup_in_parent(key) @parent.nil? ? nil : @parent.lookup(key) end def lookup(key) if x = injector_entries[key] return x end @parent ? @parent.lookup(key) : nil end # @api private def self.format_binding(b) type_name = Puppet::Pops::Types::TypeCalculator.new().string(b.type) layer_name, bindings_name = get_named_binding_layer_and_name(b) "binding: '#{type_name}/#{b.name}' in: '#{bindings_name}' in layer: '#{layer_name}'" end # @api private def self.format_contribution_source(b) layer_name, bindings_name = get_named_binding_layer_and_name(b) "(layer: #{layer_name}, bindings: #{bindings_name})" end # @api private def self.get_named_binding_layer_and_name(b) return ['<unknown>', '<unknown>'] if b.nil? return [get_named_layer(b), b.name] if b.is_a?(Puppet::Pops::Binder::Bindings::NamedBindings) get_named_binding_layer_and_name(b.eContainer) end # @api private def self.get_named_layer(b) return '<unknown>' if b.nil? return b.name if b.is_a?(Puppet::Pops::Binder::Bindings::NamedLayer) get_named_layer(b.eContainer) end # Processes the information in a layer, aggregating it to the injector_entries hash in its parent binder. # A LayerProcessor holds the intermediate state required while processing one layer. # # @api private # class LayerProcessor attr :bindings attr :binder attr :key_factory attr :contributions attr :binder_precedence def initialize(binder, key_factory) @binder = binder @binder_precedence = binder.binder_precedence @key_factory = key_factory @bindings = [] @contributions = [] @@bind_visitor ||= Puppet::Pops::Visitor.new(nil,"bind",0,0) end # Add the binding to the list of potentially effective bindings from this layer # @api private # def add(b) bindings << Puppet::Pops::Binder::InjectorEntry.new(b, binder_precedence) end # Add a multibind contribution # @api private # def add_contribution(b) contributions << Puppet::Pops::Binder::InjectorEntry.new(b, binder_precedence) end # Bind given abstract binding # @api private # def bind(binding) @@bind_visitor.visit_this(self, binding) end # @return [Puppet::Pops::Binder::InjectorEntry] the entry with the highest precedence # @api private def highest(b1, b2) if b1.is_abstract? != b2.is_abstract? # if one is abstract and the other is not, the non abstract wins b1.is_abstract? ? b2 : b1 else case b1.precedence <=> b2.precedence when 1 b1 when -1 b2 when 0 raise_conflicting_binding(b1, b2) end end end # Raises a conflicting bindings error given two InjectorEntry's with same precedence in the same layer # (if they are in different layers, something is seriously wrong) def raise_conflicting_binding(b1, b2) b1_layer_name, b1_bindings_name = binder.class.get_named_binding_layer_and_name(b1.binding) b2_layer_name, b2_bindings_name = binder.class.get_named_binding_layer_and_name(b2.binding) finality_msg = (b1.is_final? || b2.is_final?) ? ". Override of final binding not allowed" : '' # TODO: Use of layer_name is not very good, it is not guaranteed to be unique unless b1_layer_name == b2_layer_name raise ArgumentError, [ 'Conflicting binding for', "'#{b1.binding.name}'", 'being resolved across layers', "'#{b1_layer_name}' and", "'#{b2_layer_name}'" ].join(' ')+finality_msg end # Conflicting bindings made from the same source if b1_bindings_name == b2_bindings_name raise ArgumentError, [ 'Conflicting binding for name:', "'#{b1.binding.name}'", 'in layer:', "'#{b1_layer_name}', ", 'both from:', "'#{b1_bindings_name}'" ].join(' ')+finality_msg end # Conflicting bindings from different sources raise ArgumentError, [ 'Conflicting binding for name:', "'#{b1.binding.name}'", 'in layer:', "'#{b1_layer_name}',", 'from:', "'#{b1_bindings_name}', and", "'#{b2_bindings_name}'" ].join(' ')+finality_msg end # Produces the key for the given Binding. # @param binding [Puppet::Pops::Binder::Bindings::Binding] the binding to get a key for # @return [Object] an opaque key # @api private # def key(binding) k = if is_contribution?(binding) # contributions get a unique (sequential) key binder.next_anonymous_key() else key_factory.binding_key(binding) end end # @api private def is_contribution?(binding) ! binding.multibind_id.nil? end # @api private def bind_Binding(o) if is_contribution?(o) add_contribution(o) else add(o) end end # @api private def bind_Bindings(o) o.bindings.each {|b| bind(b) } end # @api private def bind_NamedBindings(o) # Name is ignored here, it should be introspected when needed (in case of errors) o.bindings.each {|b| bind(b) } end # Process layered bindings from highest to lowest layer # @api private # def bind_LayeredBindings(o) o.layers.each do |layer| processor = LayerProcessor.new(binder, key_factory) # All except abstract (==error) are transfered to injector_entries processor.bind(layer).each do |k, v| entry = binder.injector_entries[k] unless key_factory.is_contributions_key?(k) if v.is_abstract?() layer_name, bindings_name = Puppet::Pops::Binder::Binder.get_named_binding_layer_and_name(v.binding) type_name = key_factory.type_calculator.string(v.binding.type) raise ArgumentError, "The abstract binding '#{type_name}/#{v.binding.name}' in '#{bindings_name}' in layer '#{layer_name}' was not overridden" end raise ArgumentError, "Internal Error - redefinition of key: #{k}, (should never happen)" if entry binder.injector_entries[k] = v else entry ? entry << v : binder.injector_entries[k] = v end end end end # Processes one named ("top level") layer consisting of a list of NamedBindings # @api private # def bind_NamedLayer(o) o.bindings.each {|b| bind(b) } this_layer = {} # process regular bindings bindings.each do |b| bkey = key(b.binding) # ignore if a higher layer defined it (unless the lower is final), but ensure override gets resolved # (override is not resolved across binders) if x = binder.injector_entries[bkey] if b.is_final? raise_conflicting_binding(x, b) end x.mark_override_resolved() next end # If a lower (parent) binder exposes a final binding it may not be overridden # if (x = binder.lookup_in_parent(bkey)) && x.is_final? raise_conflicting_binding(x, b) end # if already found in this layer, one wins (and resolves override), or it is an error existing = this_layer[bkey] winner = existing ? highest(existing, b) : b this_layer[bkey] = winner if existing winner.mark_override_resolved() end end # Process contributions # - organize map multibind_id to bindings with this id # - for each id, create an array with the unique anonymous keys to the contributed bindings # - bind the index to a special multibind contributions key (these are aggregated) # c_hash = Hash.new {|hash, key| hash[ key ] = [] } contributions.each {|b| c_hash[ b.binding.multibind_id ] << b } # - for each id c_hash.each do |k, v| index = v.collect do |b| bkey = key(b.binding) this_layer[bkey] = b bkey end contributions_key = key_factory.multibind_contributions(k) unless this_layer[contributions_key] this_layer[contributions_key] = [] end this_layer[contributions_key] += index end this_layer end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/binder_issues.rb������������������������������������������������0000664�0052762�0001160�00000010054�12650174557�022444� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# @api public module Puppet::Pops::Binder::BinderIssues # NOTE: The methods #issue and #hard_issue are done in a somewhat funny way # since the Puppet::Pops::Issues is a module with these methods defined on the module-class # This makes it hard to inherit them in this module. (Likewise if Issues was a class, and they # need to be defined for the class, and such methods are also not inherited, it becomes more # difficult to reuse these. It did not seem as a good idea to refactor Issues at this point # in time - they should both probably be refactored once bindings support is finished. # Meanwhile, they delegate to Issues. # (see Puppet::Pops::Issues#issue) def self.issue (issue_code, *args, &block) Puppet::Pops::Issues.issue(issue_code, *args, &block) end # (see Puppet::Pops::Issues#hard_issue) def self.hard_issue(issue_code, *args, &block) Puppet::Pops::Issues.hard_issue(issue_code, *args, &block) end # Producer issues (binding identified using :binding argument) # @api public MISSING_NAME = issue :MISSING_NAME, :binding do "#{label.a_an_uc(binding)} with #{label.a_an(semantic)} has no name" end # @api public MISSING_KEY = issue :MISSING_KEY, :binding do "#{label.a_an_uc(binding)} with #{label.a_an(semantic)} has no key" end # @api public MISSING_VALUE = issue :MISSING_VALUE, :binding do "#{label.a_an_uc(binding)} with #{label.a_an(semantic)} has no value" end # @api public MISSING_EXPRESSION = issue :MISSING_EXPRESSION, :binding do "#{label.a_an_uc(binding)} with #{label.a_an(semantic)} has no expression" end # @api public MISSING_CLASS_NAME = issue :MISSING_CLASS_NAME, :binding do "#{label.a_an_uc(binding)} with #{label.a_an(semantic)} has no class name" end # @api public CACHED_PRODUCER_MISSING_PRODUCER = issue :PRODUCER_MISSING_PRODUCER, :binding do "#{label.a_an_uc(binding)} with #{label.a_an(semantic)} has no producer" end # @api public INCOMPATIBLE_TYPE = issue :INCOMPATIBLE_TYPE, :binding, :expected_type, :actual_type do "#{label.a_an_uc(binding)} with #{label.a_an(semantic)} has an incompatible type: expected #{label.a_an(expected_type)}, but got #{label.a_an(actual_type)}." end # @api public MULTIBIND_INCOMPATIBLE_TYPE = issue :MULTIBIND_INCOMPATIBLE_TYPE, :binding, :actual_type do "#{label.a_an_uc(binding)} with #{label.a_an(semantic)} cannot bind #{label.a_an(actual_type)} value" end # @api public MODEL_OBJECT_IS_UNBOUND = issue :MODEL_OBJECT_IS_UNBOUND do "#{label.a_an_uc(semantic)} is not contained in a binding" end # Binding issues (binding identified using semantic) # @api public MISSING_PRODUCER = issue :MISSING_PRODUCER do "#{label.a_an_uc(semantic)} has no producer" end # @api public MISSING_TYPE = issue :MISSING_TYPE do "#{label.a_an_uc(semantic)} has no type" end # @api public MULTIBIND_NOT_COLLECTION_PRODUCER = issue :MULTIBIND_NOT_COLLECTION_PRODUCER, :actual_producer do "#{label.a_an_uc(semantic)} must have a MultibindProducerDescriptor, but got: #{label.a_an(actual_producer)}" end # @api public MULTIBIND_TYPE_ERROR = issue :MULTIBIND_TYPE_ERROR, :actual_type do "#{label.a_an_uc(semantic)} is expected to bind a collection type, but got: #{label.a_an(actual_type)}." end # @api public MISSING_BINDINGS = issue :MISSING_BINDINGS do "#{label.a_an_uc(semantic)} has zero bindings" end # @api public MISSING_BINDINGS_NAME = issue :MISSING_BINDINGS_NAME do "#{label.a_an_uc(semantic)} has no name" end # @api public MISSING_PREDICATES = issue :MISSING_PREDICATES do "#{label.a_an_uc(semantic)} has zero predicates" end # @api public MISSING_LAYERS = issue :MISSING_LAYERS do "#{label.a_an_uc(semantic)} has zero layers" end # @api public MISSING_LAYER_NAME = issue :MISSING_LAYER_NAME do "#{label.a_an_uc(semantic)} has a layer without name" end # @api public MISSING_BINDINGS_IN_LAYER = issue :MISSING_BINDINGS_IN_LAYER, :layer do "#{label.a_an_uc(semantic)} has zero bindings in #{label.label(layer)}" end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/bindings_checker.rb���������������������������������������������0000664�0052762�0001160�00000015216�12650174557�023074� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A validator/checker of a bindings model # @api public # class Puppet::Pops::Binder::BindingsChecker Bindings = Puppet::Pops::Binder::Bindings Issues = Puppet::Pops::Binder::BinderIssues Types = Puppet::Pops::Types attr_reader :type_calculator attr_reader :acceptor # @api public def initialize(diagnostics_producer) @@check_visitor ||= Puppet::Pops::Visitor.new(nil, "check", 0, 0) @type_calculator = Puppet::Pops::Types::TypeCalculator.new() @expression_validator = Puppet::Pops::Validation::ValidatorFactory_4_0.new().checker(diagnostics_producer) @acceptor = diagnostics_producer 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. # # @api public # def validate(b) check(b) b.eAllContents.each {|c| check(c) } end # Performs binding validity check # @api private def check(b) @@check_visitor.visit_this_0(self, b) end # Checks that a binding has a producer and a type # @api private def check_Binding(b) # Must have a type acceptor.accept(Issues::MISSING_TYPE, b) unless b.type.is_a?(Types::PAnyType) # Must have a producer acceptor.accept(Issues::MISSING_PRODUCER, b) unless b.producer.is_a?(Bindings::ProducerDescriptor) end # Checks that the producer is a Multibind producer and that the type is a PCollectionType # @api private def check_Multibinding(b) # id is optional (empty id blocks contributions) # A multibinding must have PCollectionType acceptor.accept(Issues::MULTIBIND_TYPE_ERROR, b, {:actual_type => b.type}) unless b.type.is_a?(Types::PCollectionType) # if the producer is nil, a suitable producer will be picked automatically unless b.producer.nil? || b.producer.is_a?(Bindings::MultibindProducerDescriptor) acceptor.accept(Issues::MULTIBIND_NOT_COLLECTION_PRODUCER, b, {:actual_producer => b.producer}) end end # Checks that the bindings object contains at least one binding. Then checks each binding in turn # @api private def check_Bindings(b) acceptor.accept(Issues::MISSING_BINDINGS, b) unless has_entries?(b.bindings) end # Checks that a name has been associated with the bindings # @api private def check_NamedBindings(b) acceptor.accept(Issues::MISSING_BINDINGS_NAME, b) unless has_chars?(b.name) check_Bindings(b) end # Check layer has a name # @api private def check_NamedLayer(l) acceptor.accept(Issues::MISSING_LAYER_NAME, binding_parent(l)) unless has_chars?(l.name) end # Checks that the binding has layers and that each layer has a name and at least one binding # @api private def check_LayeredBindings(b) acceptor.accept(Issues::MISSING_LAYERS, b) unless has_entries?(b.layers) end # Checks that the non caching producer has a producer to delegate to # @api private def check_NonCachingProducerDescriptor(p) acceptor.accept(Issues::PRODUCER_MISSING_PRODUCER, p) unless p.producer.is_a?(Bindings::ProducerDescriptor) end # Checks that a constant value has been declared in the producer and that the type # of the value is compatible with the type declared in the binding # @api private def check_ConstantProducerDescriptor(p) # the product must be of compatible type # TODO: Likely to change when value becomes a typed Puppet Object b = binding_parent(p) if p.value.nil? acceptor.accept(Issues::MISSING_VALUE, p, {:binding => b}) else infered = type_calculator.infer(p.value) unless type_calculator.assignable?(b.type, infered) acceptor.accept(Issues::INCOMPATIBLE_TYPE, p, {:binding => b, :expected_type => b.type, :actual_type => infered}) end end end # Checks that an expression has been declared in the producer # @api private def check_EvaluatingProducerDescriptor(p) unless p.expression.is_a?(Puppet::Pops::Model::Expression) acceptor.accept(Issues::MISSING_EXPRESSION, p, {:binding => binding_parent(p)}) end end # Checks that a class name has been declared in the producer # @api private def check_InstanceProducerDescriptor(p) acceptor.accept(Issues::MISSING_CLASS_NAME, p, {:binding => binding_parent(p)}) unless has_chars?(p.class_name) end # Checks that a type and a name has been declared. The type must be assignable to the type # declared in the binding. The name can be an empty string to denote 'no name' # @api private def check_LookupProducerDescriptor(p) b = binding_parent(p) unless type_calculator.assignable(b.type, p.type) acceptor.accept(Issues::INCOMPATIBLE_TYPE, p, {:binding => b, :expected_type => b.type, :actual_type => p.type }) end acceptor.accept(Issues::MISSING_NAME, p, {:binding => b}) if p.name.nil? # empty string is OK end # Checks that a key has been declared, then calls producer_LookupProducerDescriptor to perform # checks associated with the super class # @api private def check_HashLookupProducerDescriptor(p) acceptor.accept(Issues::MISSING_KEY, p, {:binding => binding_parent(p)}) unless has_chars?(p.key) check_LookupProducerDescriptor(p) end # Checks that the type declared in the binder is a PArrayType # @api private def check_ArrayMultibindProducerDescriptor(p) b = binding_parent(p) acceptor.accept(Issues::MULTIBIND_INCOMPATIBLE_TYPE, p, {:binding => b, :actual_type => b.type}) unless b.type.is_a?(Types::PArrayType) end # Checks that the type declared in the binder is a PHashType # @api private def check_HashMultibindProducerDescriptor(p) b = binding_parent(p) acceptor.accept(Issues::MULTIBIND_INCOMPATIBLE_TYPE, p, {:binding => b, :actual_type => b.type}) unless b.type.is_a?(Types::PHashType) end # Checks that the producer that this producer delegates to is declared # @api private def check_ProducerProducerDescriptor(p) unless p.producer.is_a?(Bindings::ProducerDescriptor) acceptor.accept(Issues::PRODUCER_MISSING_PRODUCER, p, {:binding => binding_parent(p)}) end end # @api private def check_Expression(t) @expression_validator.validate(t) end # @api private def check_PAnyType(t) # Do nothing end # Returns true if the argument is a non empty string # @api private def has_chars?(s) s.is_a?(String) && !s.empty? end # @api private def has_entries?(s) !(s.nil? || s.empty?) end # @api private def binding_parent(p) begin x = p.eContainer if x.nil? acceptor.accept(Issues::MODEL_OBJECT_IS_UNBOUND, p) return nil end p = x end while !p.is_a?(Bindings::AbstractBinding) p end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/bindings_composer.rb��������������������������������������������0000664�0052762�0001160�00000015304�12650174557�023315� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The BindingsComposer handles composition of multiple bindings sources # It is directed by a {Puppet::Pops::Binder::Config::BinderConfig BinderConfig} that indicates how # the final composition should be layered, and what should be included/excluded in each layer # # The bindings composer is intended to be used once per environment as the compiler starts its work. # # TODO: Possibly support envdir: scheme / relative to environment root (== same as confdir if there is only one environment). # This is probably easier to do after ENC changes described in ARM-8 have been implemented. # TODO: If same config is loaded in a higher layer, skip it in the lower (since it is meaningless to load it again with lower # precedence. (Optimization, or possibly an error, should produce a warning). # class Puppet::Pops::Binder::BindingsComposer # The BindingsConfig instance holding the read and parsed, but not evaluated configuration # @api public # attr_reader :config # map of scheme name to handler # @api private attr_reader :scheme_handlers # @return [Hash{String => Puppet::Module}] map of module name to module instance # @api private attr_reader :name_to_module # @api private attr_reader :confdir # @api private attr_reader :diagnostics # Container of all warnings and errors produced while initializing and loading bindings # # @api public attr_reader :acceptor # @api public def initialize() @acceptor = Puppet::Pops::Validation::Acceptor.new() @diagnostics = Puppet::Pops::Binder::Config::DiagnosticProducer.new(acceptor) @config = Puppet::Pops::Binder::Config::BinderConfig.new(@diagnostics) if acceptor.errors? Puppet::Pops::IssueReporter.assert_and_report(acceptor, :message => 'Binding Composer: error while reading config.') raise Puppet::DevError.new("Internal Error: IssueReporter did not raise exception for errors in bindings config.") end end # Configures and creates the boot injector. # The read config may optionally contain mapping of bindings scheme handler name to handler class, and # mapping of biera2 backend symbolic name to backend class. # If present, these are turned into bindings in the category 'extension' (which is only used in the boot injector) which # has higher precedence than 'default'. This is done to allow users to override the default bindings for # schemes and backends. # @param scope [Puppet::Parser:Scope] the scope (used to find compiler and injector for the environment) # @api private # def configure_and_create_injector(scope) # create the injector (which will pick up the bindings registered above) @scheme_handlers = SchemeHandlerHelper.new(scope) # get extensions from the config # ------------------------------ scheme_extensions = @config.scheme_extensions # Define a named bindings that are known by the SystemBindings boot_bindings = Puppet::Pops::Binder::BindingsFactory.named_bindings(Puppet::Pops::Binder::SystemBindings::ENVIRONMENT_BOOT_BINDINGS_NAME) do scheme_extensions.each_pair do |scheme, class_name| # turn each scheme => class_name into a binding (contribute to the buildings-schemes multibind). # do this in category 'extensions' to allow them to override the 'default' bind do name(scheme) instance_of(::Puppetx::BINDINGS_SCHEMES_TYPE) in_multibind(::Puppetx::BINDINGS_SCHEMES) to_instance(class_name) end end end @injector = scope.compiler.create_boot_injector(boot_bindings.model) end # @return [Puppet::Pops::Binder::Bindings::LayeredBindings] def compose(scope) # The boot injector is used to lookup scheme-handlers configure_and_create_injector(scope) # get all existing modules and their root path @name_to_module = {} scope.environment.modules.each {|mod| name_to_module[mod.name] = mod } # setup the confdir @confdir = Puppet.settings[:confdir] factory = Puppet::Pops::Binder::BindingsFactory contributions = [] configured_layers = @config.layering_config.collect do | layer_config | # get contributions contribs = configure_layer(layer_config, scope, diagnostics) # collect the contributions separately for later checking of category precedence contributions.concat(contribs) # create a named layer with all the bindings for this layer factory.named_layer(layer_config['name'], *contribs.collect {|c| c.bindings }.flatten) end # Add the two system layers; the final - highest ("can not be overridden" layer), and the lowest # Everything here can be overridden 'default' layer. # configured_layers.insert(0, Puppet::Pops::Binder::SystemBindings.final_contribution) configured_layers.insert(-1, Puppet::Pops::Binder::SystemBindings.default_contribution) # and finally... create the resulting structure factory.layered_bindings(*configured_layers) end private def configure_layer(layer_description, scope, diagnostics) name = layer_description['name'] # compute effective set of uris to load (and get rid of any duplicates in the process included_uris = array_of_uris(layer_description['include']) excluded_uris = array_of_uris(layer_description['exclude']) effective_uris = Set.new(expand_included_uris(included_uris)).subtract(Set.new(expand_excluded_uris(excluded_uris))) # Each URI should result in a ContributedBindings effective_uris.collect { |uri| scheme_handlers[uri.scheme].contributed_bindings(uri, scope, self) } end def array_of_uris(descriptions) return [] unless descriptions descriptions = [descriptions] unless descriptions.is_a?(Array) descriptions.collect {|d| URI.parse(d) } end def expand_included_uris(uris) result = [] uris.each do |uri| unless handler = scheme_handlers[uri.scheme] raise ArgumentError, "Unknown bindings provider scheme: '#{uri.scheme}'" end result.concat(handler.expand_included(uri, self)) end result end def expand_excluded_uris(uris) result = [] uris.each do |uri| unless handler = scheme_handlers[uri.scheme] raise ArgumentError, "Unknown bindings provider scheme: '#{uri.scheme}'" end result.concat(handler.expand_excluded(uri, self)) end result end class SchemeHandlerHelper T = Puppet::Pops::Types::TypeFactory HASH_OF_HANDLER = T.hash_of(T.type_of('Puppetx::Puppet::BindingsSchemeHandler')) def initialize(scope) @scope = scope @cache = nil end def [] (scheme) load_schemes unless @cache @cache[scheme] end def load_schemes @cache = @scope.compiler.boot_injector.lookup(@scope, HASH_OF_HANDLER, Puppetx::BINDINGS_SCHEMES) || {} end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/bindings_factory.rb���������������������������������������������0000664�0052762�0001160�00000075654�12650174557�023153� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A helper class that makes it easier to construct a Bindings model. # # The Bindings Model # ------------------ # The BindingsModel (defined in {Puppet::Pops::Binder::Bindings} is a model that is intended to be generally free from Ruby concerns. # This means that it is possible for system integrators to create and serialize such models using other technologies than # Ruby. This manifests itself in the model in that producers are described using instances of a `ProducerDescriptor` rather than # describing Ruby classes directly. This is also true of the type system where type is expressed using the {Puppet::Pops::Types} model # to describe all types. # # This class, the `BindingsFactory` is a concrete Ruby API for constructing instances of classes in the model. # # Named Bindings # -------------- # The typical usage of the factory is to call {named_bindings} which creates a container of bindings wrapped in a *build object* # equipped with convenience methods to define the details of the just created named bindings. # The returned builder is an instance of {Puppet::Pops::Binder::BindingsFactory::BindingsContainerBuilder BindingsContainerBuilder}. # # Binding # ------- # A Binding binds a type/name key to a producer of a value. A binding is conveniently created by calling `bind` on a # `BindingsContainerBuilder`. The call to bind, produces a binding wrapped in a build object equipped with convenience methods # to define the details of the just created binding. The returned builder is an instance of # {Puppet::Pops::Binder::BindingsFactory::BindingsBuilder BindingsBuilder}. # # Multibinding # ------------ # A multibinding works like a binding, but it requires an additional ID. It also places constraints on the type of the binding; # it must be a collection type (Hash or Array). # # Constructing and Contributing Bindings from Ruby # ------------------------------------------------ # The bindings system is used by referencing bindings symbolically; these are then specified in a Ruby file which is autoloaded # by Puppet. The entry point for user code that creates bindings is described in {Puppet::Bindings Bindings}. # That class makes use of a BindingsFactory, and the builder objects to make it easy to construct bindings. # # It is intended that a user defining bindings in Ruby should be able to use the builder object methods for the majority of tasks. # If something advanced is wanted, use of one of the helper class methods on the BuildingsFactory, and/or the # {Puppet::Pops::Types::TypeCalculator TypeCalculator} will be required to create and configure objects that are not handled by # the methods in the builder objects. # # Chaining of calls # ------------------ # Since all the build methods return the build object it is easy to stack on additional calls. The intention is to # do this in an order that is readable from left to right: `bind.string.name('thename').to(42)`, but there is nothing preventing # making the calls in some other order e.g. `bind.to(42).name('thename').string`, the second is quite unreadable but produces # the same result. # # For sake of human readability, the method `name` is alsp available as `named`, with the intention that it is used after a type, # e.g. `bind.integer.named('the meaning of life').to(42)` # # Methods taking blocks # ---------------------- # Several methods take an optional block. The block evaluates with the builder object as `self`. This means that there is no # need to chain the methods calls, they can instead be made in sequence - e.g. # # bind do # integer # named 'the meaning of life' # to 42 # end # # or mix the two styles # # bind do # integer.named 'the meaning of life' # to 42 # end # # Unwrapping the result # --------------------- # The result from all methods is a builder object. Call the method `model` to unwrap the constructed bindings model object. # # bindings = BindingsFactory.named_bindings('my named bindings') do # # bind things # end.model # # @example Create a NamedBinding with content # result = Puppet::Pops::Binder::BindingsFactory.named_bindings("mymodule::mybindings") do # bind.name("foo").to(42) # bind.string.name("site url").to("http://www.example.com") # end # result.model() # # @api public # module Puppet::Pops::Binder::BindingsFactory # Alias for the {Puppet::Pops::Types::TypeFactory TypeFactory}. This is also available as the method # `type_factory`. # T = Puppet::Pops::Types::TypeFactory # Abstract base class for bindings object builders. # Supports delegation of method calls to the BindingsFactory class methods for all methods not implemented # by a concrete builder. # # @abstract # class AbstractBuilder # The built model object. attr_reader :model # @param binding [Puppet::Pops::Binder::Bindings::AbstractBinding] The binding to build. # @api public def initialize(binding) @model = binding end # Provides convenient access to the Bindings Factory class methods. The intent is to provide access to the # methods that return producers for the purpose of composing more elaborate things than the builder convenience # methods support directly. # @api private # def method_missing(meth, *args, &block) factory = Puppet::Pops::Binder::BindingsFactory if factory.respond_to?(meth) factory.send(meth, *args, &block) else super end end end # A bindings builder for an AbstractBinding containing other AbstractBinding instances. # @api public class BindingsContainerBuilder < AbstractBuilder # Adds an empty binding to the container, and returns a builder for it for further detailing. # An optional block may be given which is evaluated using `instance_eval`. # @return [BindingsBuilder] the builder for the created binding # @api public # def bind(&block) binding = Puppet::Pops::Binder::Bindings::Binding.new() model.addBindings(binding) builder = BindingsBuilder.new(binding) builder.instance_eval(&block) if block_given? builder end # Binds a multibind with the given identity where later, the looked up result contains all # contributions to this key. An optional block may be given which is evaluated using `instance_eval`. # @param id [String] the multibind's id used when adding contributions # @return [MultibindingsBuilder] the builder for the created multibinding # @api public # def multibind(id, &block) binding = Puppet::Pops::Binder::Bindings::Multibinding.new() binding.id = id model.addBindings(binding) builder = MultibindingsBuilder.new(binding) builder.instance_eval(&block) if block_given? builder end end # Builds a Binding via convenience methods. # # @api public # class BindingsBuilder < AbstractBuilder # @param binding [Puppet::Pops::Binder::Bindings::AbstractBinding] the binding to build. # @api public def initialize(binding) super binding data() end # Sets the name of the binding. # @param name [String] the name to bind. # @api public def name(name) model.name = name self end # Same as {#name}, but reads better in certain combinations. # @api public alias_method :named, :name # Sets the binding to be abstract (it must be overridden) # @api public def abstract model.abstract = true self end # Sets the binding to be override (it must override something) # @api public def override model.override = true self end # Sets the binding to be final (it may not be overridden) # @api public def final model.final = true self end # Makes the binding a multibind contribution to the given multibind id # @param id [String] the multibind id to contribute this binding to # @api public def in_multibind(id) model.multibind_id = id self end # Sets the type of the binding to the given type. # @note # This is only needed if something other than the default type `Data` is wanted, or if the wanted type is # not provided by one of the convenience methods {#array_of_data}, {#boolean}, {#float}, {#hash_of_data}, # {#integer}, {#scalar}, {#pattern}, {#string}, or one of the collection methods {#array_of}, or {#hash_of}. # # To create a type, use the method {#type_factory}, to obtain the type. # @example creating a Hash with Integer key and Array[Integer] element type # tc = type_factory # type(tc.hash(tc.array_of(tc.integer), tc.integer) # @param type [Puppet::Pops::Types::PAnyType] the type to set for the binding # @api public # def type(type) model.type = type self end # Sets the type of the binding to Integer. # @return [Puppet::Pops::Types::PIntegerType] the type # @api public def integer() type(T.integer()) end # Sets the type of the binding to Float. # @return [Puppet::Pops::Types::PFloatType] the type # @api public def float() type(T.float()) end # Sets the type of the binding to Boolean. # @return [Puppet::Pops::Types::PBooleanType] the type # @api public def boolean() type(T.boolean()) end # Sets the type of the binding to String. # @return [Puppet::Pops::Types::PStringType] the type # @api public def string() type(T.string()) end # Sets the type of the binding to Pattern. # @return [Puppet::Pops::Types::PRegexpType] the type # @api public def pattern() type(T.pattern()) end # Sets the type of the binding to the abstract type Scalar. # @return [Puppet::Pops::Types::PScalarType] the type # @api public def scalar() type(T.scalar()) end # Sets the type of the binding to the abstract type Data. # @return [Puppet::Pops::Types::PDataType] the type # @api public def data() type(T.data()) end # Sets the type of the binding to Array[Data]. # @return [Puppet::Pops::Types::PArrayType] the type # @api public def array_of_data() type(T.array_of_data()) end # Sets the type of the binding to Array[T], where T is given. # @param t [Puppet::Pops::Types::PAnyType] the type of the elements of the array # @return [Puppet::Pops::Types::PArrayType] the type # @api public def array_of(t) type(T.array_of(t)) end # Sets the type of the binding to Hash[Literal, Data]. # @return [Puppet::Pops::Types::PHashType] the type # @api public def hash_of_data() type(T.hash_of_data()) end # Sets type of the binding to `Hash[Literal, t]`. # To also limit the key type, use {#type} and give it a fully specified # hash using {#type_factory} and then `hash_of(value_type, key_type)`. # @return [Puppet::Pops::Types::PHashType] the type # @api public def hash_of(t) type(T.hash_of(t)) end # Sets the type of the binding based on the given argument. # @overload instance_of(t) # The same as calling {#type} with `t`. # @param t [Puppet::Pops::Types::PAnyType] the type # @overload instance_of(o) # Infers the type from the given Ruby object and sets that as the type - i.e. "set the type # of the binding to be that of the given data object". # @param o [Object] the object to infer the type from # @overload instance_of(c) # @param c [Class] the Class to base the type on. # Sets the type based on the given ruby class. The result is one of the specific puppet types # if the class can be represented by a specific type, or the open ended PRuntimeType otherwise. # @overload instance_of(s) # The same as using a class, but instead of giving a class instance, the class is expressed using its fully # qualified name. This method of specifying the type allows late binding (the class does not have to be loaded # before it can be used in a binding). # @param s [String] the fully qualified classname to base the type on. # @return the resulting type # @api public # def instance_of(t) type(T.type_of(t)) end # Provides convenient access to the type factory. # This is intended to be used when methods taking a type as argument i.e. {#type}, {#array_of}, {#hash_of}, and {#instance_of}. # @note # The type factory is also available via the constant {T}. # @api public def type_factory Puppet::Pops::Types::TypeFactory end # Sets the binding's producer to a singleton producer, if given argument is a value, a literal producer is created for it. # To create a producer producing an instance of a class with lazy loading of the class, use {#to_instance}. # # @overload to(a_literal) # Sets a constant producer in the binding. # @overload to(a_class, *args) # Sets an Instantiating producer (producing an instance of the given class) # @overload to(a_producer_descriptor) # Sets the producer from the given producer descriptor # @return [BindingsBuilder] self # @api public # def to(producer, *args) case producer when Class producer = Puppet::Pops::Binder::BindingsFactory.instance_producer(producer.name, *args) when Puppet::Pops::Model::Program # program is not an expression producer = Puppet::Pops::Binder::BindingsFactory.evaluating_producer(producer.body) when Puppet::Pops::Model::Expression producer = Puppet::Pops::Binder::BindingsFactory.evaluating_producer(producer) when Puppet::Pops::Binder::Bindings::ProducerDescriptor else # If given producer is not a producer, create a literal producer producer = Puppet::Pops::Binder::BindingsFactory.literal_producer(producer) end model.producer = producer self end # Sets the binding's producer to a producer of an instance of given class (a String class name, or a Class instance). # Use a string class name when lazy loading of the class is wanted. # # @overload to_instance(class_name, *args) # @param class_name [String] the name of the class to instantiate # @param args [Object] optional arguments to the constructor # @overload to_instance(a_class) # @param a_class [Class] the class to instantiate # @param args [Object] optional arguments to the constructor # def to_instance(type, *args) class_name = case type when Class type.name when String type else raise ArgumentError, "to_instance accepts String (a class name), or a Class.*args got: #{type.class}." end model.producer = Puppet::Pops::Binder::BindingsFactory.instance_producer(class_name, *args) end # Sets the binding's producer to a singleton producer # @overload to_producer(a_producer) # Sets the producer to an instantiated producer. The resulting model can not be serialized as a consequence as there # is no meta-model describing the specialized producer. Use this only in exceptional cases, or where there is never the # need to serialize the model. # @param a_producer [Puppet::Pops::Binder::Producers::Producer] an instantiated producer, not serializeable ! # # @overload to_producer(a_class, *args) # @param a_class [Class] the class to create an instance of # @param args [Object] the arguments to the given class' new # # @overload to_producer(a_producer_descriptor) # @param a_producer_descriptor [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a descriptor # producing Puppet::Pops::Binder::Producers::Producer # # @api public # def to_producer(producer, *args) case producer when Class producer = Puppet::Pops::Binder::BindingsFactory.instance_producer(producer.name, *args) when Puppet::Pops::Binder::Bindings::ProducerDescriptor when Puppet::Pops::Binder::Producers::Producer # a custom producer instance producer = Puppet::Pops::Binder::BindingsFactory.literal_producer(producer) else raise ArgumentError, "Given producer argument is none of a producer descriptor, a class, or a producer" end metaproducer = Puppet::Pops::Binder::BindingsFactory.producer_producer(producer) model.producer = metaproducer self end # Sets the binding's producer to a series of producers. # Use this when you want to produce a different producer on each request for a producer # # @overload to_producer(a_producer) # Sets the producer to an instantiated producer. The resulting model can not be serialized as a consequence as there # is no meta-model describing the specialized producer. Use this only in exceptional cases, or where there is never the # need to serialize the model. # @param a_producer [Puppet::Pops::Binder::Producers::Producer] an instantiated producer, not serializeable ! # # @overload to_producer(a_class, *args) # @param a_class [Class] the class to create an instance of # @param args [Object] the arguments to the given class' new # # @overload to_producer(a_producer_descriptor) # @param a_producer_descriptor [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a descriptor # producing Puppet::Pops::Binder::Producers::Producer # # @api public # def to_producer_series(producer, *args) case producer when Class producer = Puppet::Pops::Binder::BindingsFactory.instance_producer(producer.name, *args) when Puppet::Pops::Binder::Bindings::ProducerDescriptor when Puppet::Pops::Binder::Producers::Producer # a custom producer instance producer = Puppet::Pops::Binder::BindingsFactory.literal_producer(producer) else raise ArgumentError, "Given producer argument is none of a producer descriptor, a class, or a producer" end non_caching = Puppet::Pops::Binder::Bindings::NonCachingProducerDescriptor.new() non_caching.producer = producer metaproducer = Puppet::Pops::Binder::BindingsFactory.producer_producer(non_caching) non_caching = Puppet::Pops::Binder::Bindings::NonCachingProducerDescriptor.new() non_caching.producer = metaproducer model.producer = non_caching self end # Sets the binding's producer to a "non singleton" producer (each call to produce produces a new instance/copy). # @overload to_series_of(a_literal) # a constant producer # @overload to_series_of(a_class, *args) # Instantiating producer # @overload to_series_of(a_producer_descriptor) # a given producer # # @api public # def to_series_of(producer, *args) case producer when Class producer = Puppet::Pops::Binder::BindingsFactory.instance_producer(producer.name, *args) when Puppet::Pops::Binder::Bindings::ProducerDescriptor else # If given producer is not a producer, create a literal producer producer = Puppet::Pops::Binder::BindingsFactory.literal_producer(producer) end non_caching = Puppet::Pops::Binder::Bindings::NonCachingProducerDescriptor.new() non_caching.producer = producer model.producer = non_caching self end # Sets the binding's producer to one that performs a lookup of another key # @overload to_lookup_of(type, name) # @overload to_lookup_of(name) # @api public # def to_lookup_of(type, name=nil) unless name name = type type = Puppet::Pops::Types::TypeFactory.data() end model.producer = Puppet::Pops::Binder::BindingsFactory.lookup_producer(type, name) self end # Sets the binding's producer to a one that performs a lookup of another key and they applies hash lookup on # the result. # # @overload to_lookup_of(type, name) # @overload to_lookup_of(name) # @api public # def to_hash_lookup_of(type, name, key) model.producer = Puppet::Pops::Binder::BindingsFactory.hash_lookup_producer(type, name, key) self end # Sets the binding's producer to one that produces the first found lookup of another key # @param list_of_lookups [Array] array of arrays [type name], or just name (implies data) # @example # binder.bind().name('foo').to_first_found('fee', 'fum', 'extended-bar') # binder.bind().name('foo').to_first_found( # [T.ruby(ThisClass), 'fee'], # [T.ruby(ThatClass), 'fum'], # 'extended-bar') # @api public # def to_first_found(*list_of_lookups) producers = list_of_lookups.collect do |entry| if entry.is_a?(Array) case entry.size when 2 Puppet::Pops::Binder::BindingsFactory.lookup_producer(entry[0], entry[1]) when 1 Puppet::Pops::Binder::BindingsFactory.lookup_producer(Puppet::Pops::Types::TypeFactory.data(), entry[0]) else raise ArgumentError, "Not an array of [type, name], name, or [name]" end else Puppet::Pops::Binder::BindingsFactory.lookup_producer(T.data(), entry) end end model.producer = Puppet::Pops::Binder::BindingsFactory.first_found_producer(*producers) self end # Sets options to the producer. # See the respective producer for the options it supports. All producers supports the option `:transformer`, a # puppet or ruby lambda that is evaluated with the produced result as an argument. The ruby lambda gets scope and # value as arguments. # @note # A Ruby lambda is not cross platform safe. Use a puppet lambda if you want a bindings model that is. # # @api public def producer_options(options) options.each do |k, v| arg = Puppet::Pops::Binder::Bindings::NamedArgument.new() arg.name = k.to_s arg.value = v model.addProducer_args(arg) end self end end # A builder specialized for multibind - checks that type is Array or Hash based. A new builder sets the # multibinding to be of type Hash[Data]. # # @api public class MultibindingsBuilder < BindingsBuilder # Constraints type to be one of {Puppet::Pops::Types::PArrayType PArrayType}, or {Puppet::Pops::Types::PHashType PHashType}. # @raise [ArgumentError] if type constraint is not met. # @api public def type(type) unless type.class == Puppet::Pops::Types::PArrayType || type.class == Puppet::Pops::Types::PHashType raise ArgumentError, "Wrong type; only PArrayType, or PHashType allowed, got '#{type.to_s}'" end model.type = type self end # Overrides the default implementation that will raise an exception as a multibind requires a hash type. # Thus, if nothing else is requested, a multibind will be configured as Hash[Data]. # def data() hash_of_data() end end # Produces a ContributedBindings. # A ContributedBindings is used by bindings providers to return a set of named bindings. # # @param name [String] the name of the contributed bindings (for human use in messages/logs only) # @param named_bindings [Puppet::Pops::Binder::Bindings::NamedBindings, Array<Puppet::Pops::Binder::Bindings::NamedBindings>] the # named bindings to include # def self.contributed_bindings(name, named_bindings) cb = Puppet::Pops::Binder::Bindings::ContributedBindings.new() cb.name = name named_bindings = [named_bindings] unless named_bindings.is_a?(Array) named_bindings.each {|b| cb.addBindings(b) } cb end # Creates a named binding container, the top bindings model object. # A NamedBindings is typically produced by a bindings provider. # # The created container is wrapped in a BindingsContainerBuilder for further detailing. # Unwrap the built result when done. # @api public # def self.named_bindings(name, &block) binding = Puppet::Pops::Binder::Bindings::NamedBindings.new() binding.name = name builder = BindingsContainerBuilder.new(binding) builder.instance_eval(&block) if block_given? builder end # This variant of {named_bindings} evaluates the given block as a method on an anonymous class, # thus, if the block defines methods or do something with the class itself, this does not pollute # the base class (BindingsContainerBuilder). # @api private # def self.safe_named_bindings(name, scope, &block) binding = Puppet::Pops::Binder::Bindings::NamedBindings.new() binding.name = name anon = Class.new(BindingsContainerBuilder) do def initialize(b) super b end end anon.send(:define_method, :_produce, block) builder = anon.new(binding) case block.arity when 0 builder._produce() when 1 builder._produce(scope) end builder end # Creates a literal/constant producer # @param value [Object] the value to produce # @return [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a producer description # @api public # def self.literal_producer(value) producer = Puppet::Pops::Binder::Bindings::ConstantProducerDescriptor.new() producer.value = value producer end # Creates a non caching producer # @param producer [Puppet::Pops::Binder::Bindings::Producer] the producer to make non caching # @return [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a producer description # @api public # def self.non_caching_producer(producer) p = Puppet::Pops::Binder::Bindings::NonCachingProducerDescriptor.new() p.producer = producer p end # Creates a producer producer # @param producer [Puppet::Pops::Binder::Bindings::Producer] a producer producing a Producer. # @return [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a producer description # @api public # def self.producer_producer(producer) p = Puppet::Pops::Binder::Bindings::ProducerProducerDescriptor.new() p.producer = producer p end # Creates an instance producer # An instance producer creates a new instance of a class. # If the class implements the class method `inject` this method is called instead of `new` to allow further lookups # to take place. This is referred to as *assisted inject*. If the class method `inject` is missing, the regular `new` method # is called. # # @param class_name [String] the name of the class # @param args[Object] arguments to the class' `new` method. # @return [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a producer description # @api public # def self.instance_producer(class_name, *args) p = Puppet::Pops::Binder::Bindings::InstanceProducerDescriptor.new() p.class_name = class_name args.each {|a| p.addArguments(a) } p end # Creates a Producer that looks up a value. # @param type [Puppet::Pops::Types::PAnyType] the type to lookup # @param name [String] the name to lookup # @return [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a producer description # @api public def self.lookup_producer(type, name) p = Puppet::Pops::Binder::Bindings::LookupProducerDescriptor.new() p.type = type p.name = name p end # Creates a Hash lookup producer that looks up a hash value, and then a key in the hash. # # @return [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a producer description # @param type [Puppet::Pops::Types::PAnyType] the type to lookup (i.e. a Hash of some key/value type). # @param name [String] the name to lookup # @param key [Object] the key to lookup in the looked up hash (type should comply with given key type). # @api public # def self.hash_lookup_producer(type, name, key) p = Puppet::Pops::Binder::Bindings::HashLookupProducerDescriptor.new() p.type = type p.name = name p.key = key p end # Creates a first-found producer that looks up from a given series of keys. The first found looked up # value will be produced. # @param producers [Array<Puppet::Pops::Binder::Bindings::ProducerDescriptor>] the producers to consult in given order # @return [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a producer descriptor # @api public def self.first_found_producer(*producers) p = Puppet::Pops::Binder::Bindings::FirstFoundProducerDescriptor.new() producers.each {|p2| p.addProducers(p2) } p end # Creates an evaluating producer that evaluates a puppet expression. # A puppet expression is most conveniently created by using the {Puppet::Pops::Parser::EvaluatingParser EvaluatingParser} as it performs # all set up and validation of the parsed source. Two convenience methods are used to parse an expression, or parse a ruby string # as a puppet string. See methods {puppet_expression}, {puppet_string} and {parser} for more information. # # @example producing a puppet expression # expr = puppet_string("Interpolated $fqdn", __FILE__) # # @param expression [Puppet::Pops::Model::Expression] a puppet DSL expression as producer by the eparser. # @return [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a producer descriptor # @api public # def self.evaluating_producer(expression) p = Puppet::Pops::Binder::Bindings::EvaluatingProducerDescriptor.new() p.expression = expression p end # Creates a NamedLayer. This is used by the bindings system to create a model of the layers. # # @api public # def self.named_layer(name, *bindings) result = Puppet::Pops::Binder::Bindings::NamedLayer.new() result.name = name bindings.each { |b| result.addBindings(b) } result end # Create a LayeredBindings. This is used by the bindings system to create a model of all given layers. # @param named_layers [Puppet::Pops::Binder::Bindings::NamedLayer] one or more named layers # @return [Puppet::Pops::Binder::Bindings::LayeredBindings] the constructed layered bindings. # @api public # def self.layered_bindings(*named_layers) result = Puppet::Pops::Binder::Bindings::LayeredBindings.new() named_layers.each {|b| result.addLayers(b) } result end # @return [Puppet::Pops::Parser::EvaluatingParser] a parser for puppet expressions def self.parser @parser ||= Puppet::Pops::Parser::EvaluatingParser.new() end # Parses and produces a puppet expression from the given string. # @param string [String] puppet source e.g. "1 + 2" # @param source_file [String] the source location, typically `__File__` # @return [Puppet::Pops::Model::Expression] an expression (that can be bound) # @api public # def self.puppet_expression(string, source_file) parser.parse_string(string, source_file).current end # Parses and produces a puppet string expression from the given string. # The string will automatically be quoted and special characters escaped. # As an example if given the (ruby) string "Hi\nMary" it is transformed to # the puppet string (illustrated with a ruby string) "\"Hi\\nMary\”" before being # parsed. # # @param string [String] puppet source e.g. "On node $!{fqdn}" # @param source_file [String] the source location, typically `__File__` # @return [Puppet::Pops::Model::Expression] an expression (that can be bound) # @api public # def self.puppet_string(string, source_file) parser.parse_string(parser.quote(string), source_file).current end end ������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/bindings_label_provider.rb��������������������������������������0000664�0052762�0001160�00000004773�12650174557�024467� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A provider of labels for bindings model object, producing a human name for the model object. # @api private # class Puppet::Pops::Binder::BindingsLabelProvider < Puppet::Pops::LabelProvider def initialize @@label_visitor ||= Puppet::Pops::Visitor.new(self,"label",0,0) end # Produces a label for the given object without article. # @return [String] a human readable label # def label o @@label_visitor.visit(o) end def label_PAnyType o ; "#{Puppet::Pops::Types::TypeFactory.label(o)}" end def label_ProducerDescriptor o ; "Producer" end def label_NonCachingProducerDescriptor o ; "Non Caching Producer" end def label_ConstantProducerDescriptor o ; "Producer['#{o.value}']" end def label_EvaluatingProducerDescriptor o ; "Evaluating Producer" end def label_InstanceProducerDescriptor o ; "Producer[#{o.class_name}]" end def label_LookupProducerDescriptor o ; "Lookup Producer[#{o.name}]" end def label_HashLookupProducerDescriptor o ; "Hash Lookup Producer[#{o.name}][#{o.key}]" end def label_FirstFoundProducerDescriptor o ; "First Found Producer" end def label_ProducerProducerDescriptor o ; "Producer[Producer]" end def label_MultibindProducerDescriptor o ; "Multibind Producer" end def label_ArrayMultibindProducerDescriptor o ; "Array Multibind Producer" end def label_HashMultibindProducerDescriptor o ; "Hash Multibind Producer" end def label_Bindings o ; "Bindings" end def label_NamedBindings o ; "Named Bindings" end def label_LayeredBindings o ; "Layered Bindings" end def label_NamedLayer o ; "Layer '#{o.name}'" end def label_ContributedBindings o ; "Contributed Bindings" end def label_NamedArgument o ; "Named Argument" end def label_Binding(o) 'Binding' + (o.multibind_id.nil? ? '' : ' In Multibind') end def label_Multibinding(o) 'Multibinding' + (o.multibind_id.nil? ? '' : ' In Multibind') end end �����puppet-3.8.5/lib/puppet/pops/binder/bindings_loader.rb����������������������������������������������0000664�0052762�0001160�00000005632�12650174557�022737� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'rgen/metamodel_builder' # 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 Puppet::Pops::Binder::BindingsLoader @confdir = Puppet.settings[:confdir] # @autoloader = Puppet::Util::Autoload.new("BindingsLoader", "puppet/bindings", :wrap => false) # Returns a XXXXX given a fully qualified class name. # Lookup of class is never relative to the calling namespace. # @param name [String, Array<String>, Array<Symbol>, Puppet::Pops::Types::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(scope, name) case name when String provide_from_string(scope, name) when Array provide_from_name_path(scope, name.join('::'), name) else raise ArgumentError, "Cannot provide a bindings from a '#{name.class.name}'" end end # If loadable name exists relative to a a basedir or not. Returns the loadable path as a side effect. # @return [String, nil] a loadable path for the given name, or nil # def self.loadable?(basedir, name) # note, "lib" is added by the autoloader # paths_for_name(name).find {|p| Puppet::FileSystem.exist?(File.join(basedir, "lib/puppet/bindings", p)+'.rb') } end private def self.loader() unless Puppet.settings[:confdir] == @confdir @confdir = Puppet.settings[:confdir] == @confdir @autoloader = Puppet::Util::Autoload.new("BindingsLoader", "puppet/bindings", :wrap => false) end @autoloader end def self.provide_from_string(scope, name) name_path = name.split('::') # always from the root, so remove an empty first segment if name_path[0].empty? name_path = name_path[1..-1] end provide_from_name_path(scope, name, name_path) end def self.provide_from_name_path(scope, name, name_path) # If bindings is already loaded, try this first result = Puppet::Bindings.resolve(scope, name) unless result # Attempt to load it using the auto loader paths_for_name(name).find {|path| loader.load(path) } result = Puppet::Bindings.resolve(scope, name) end result end def self.paths_for_name(fq_name) [de_camel(fq_name), downcased_path(fq_name)] end def self.downcased_path(fq_name) fq_name.to_s.gsub(/::/, '/').downcase end 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 end ������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/bindings_model.rb�����������������������������������������������0000664�0052762�0001160�00000003101�12650174557�022556� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'rgen/metamodel_builder' # The Bindings model is a model of Key to Producer mappings (bindings). # It is composed of a meta-model part (bindings_model_meta.rb), and # and implementation part (this file). # # @see Puppet::Pops::Binder::BindingsFactory The BindingsFactory for more details on how to create model instances. # @api public module Puppet::Pops::Binder require 'puppet/pops/binder/bindings_model_meta' # TODO: See PUP-2978 for possible performance optimization # Mix in implementation into the generated code module Bindings class BindingsModelObject include Puppet::Pops::Visitable include Puppet::Pops::Adaptable include Puppet::Pops::Containment end class ConstantProducerDescriptor module ClassModule def setValue(v) @value = v end def getValue() @value end def value=(v) @value = v end end end class NamedArgument module ClassModule def setValue(v) @value = v end def getValue() @value end def value=(v) @value = v end end end class InstanceProducerDescriptor module ClassModule def addArguments(val, index =-1) @arguments ||= [] @arguments.insert(index, val) end def removeArguments(val) raise "unsupported operation" end def setArguments(values) @arguments = [] values.each {|v| addArguments(v) } end end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/bindings_model_dumper.rb����������������������������������������0000664�0052762�0001160�00000010423�12650174557�024137� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� # Dumps a Pops::Binder::Bindings 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 # NOTE: use :break, :indent, :dedent in lists to do just that # class Puppet::Pops::Binder::BindingsModelDumper < Puppet::Pops::Model::TreeDumper Bindings = Puppet::Pops::Binder::Bindings attr_reader :type_calculator attr_reader :expression_dumper def initialize super @type_calculator = Puppet::Pops::Types::TypeCalculator.new() @expression_dumper = Puppet::Pops::Model::ModelTreeDumper.new() end def dump_BindingsFactory o do_dump(o.model) end def dump_BindingsBuilder o do_dump(o.model) end def dump_BindingsContainerBuilder o do_dump(o.model) end def dump_NamedLayer o result = ['named-layer', (o.name.nil? ? '<no-name>': o.name), :indent] if o.bindings o.bindings.each do |b| result << :break result << do_dump(b) end end result << :dedent result end def dump_Array o o.collect {|e| do_dump(e) } end def dump_ASTArray o ["[]"] + o.children.collect {|x| do_dump(x)} end def dump_ASTHash o ["{}"] + o.value.sort_by{|k,v| k.to_s}.collect {|x| [do_dump(x[0]), do_dump(x[1])]} end def dump_Integer o o.to_s end # Dump a Ruby String in single quotes unless it is a number. def dump_String o "'#{o}'" end def dump_NilClass o "()" end def dump_Object o ['dev-error-no-polymorph-dump-for:', o.class.to_s, o.to_s] end def is_nop? o o.nil? || o.is_a?(Model::Nop) || o.is_a?(AST::Nop) end def dump_ProducerDescriptor o result = [o.class.name] result << expression_dumper.dump(o.transformer) if o.transformer result end def dump_NonCachingProducerDescriptor o dump_ProducerDescriptor(o) + do_dump(o.producer) end def dump_ConstantProducerDescriptor o ['constant', do_dump(o.value)] end def dump_EvaluatingProducerDescriptor o result = dump_ProducerDescriptor(o) result << expression_dumper.dump(o.expression) end def dump_InstanceProducerDescriptor o # TODO: o.arguments, o. transformer ['instance', o.class_name] end def dump_ProducerProducerDescriptor o # skip the transformer lambda... result = ['producer-producer', do_dump(o.producer)] result << expression_dumper.dump(o.transformer) if o.transformer result end def dump_LookupProducerDescriptor o ['lookup', do_dump(o.type), o.name] end def dump_PAnyType o type_calculator.string(o) end def dump_HashLookupProducerDescriptor o # TODO: transformer lambda result = ['hash-lookup', do_dump(o.type), o.name, "[#{do_dump(o.key)}]"] result << expression_dumper.dump(o.transformer) if o.transformer result end def dump_FirstFoundProducerDescriptor o # TODO: transformer lambda ['first-found', do_dump(o.producers)] end def dump_ArrayMultibindProducerDescriptor o ['multibind-array'] end def dump_HashMultibindProducerDescriptor o ['multibind-hash'] end def dump_NamedArgument o "#{o.name} => #{do_dump(o.value)}" end def dump_Binding o result = ['bind'] result << 'override' if o.override result << 'abstract' if o.abstract result.concat([do_dump(o.type), o.name]) result << "(in #{o.multibind_id})" if o.multibind_id result << ['to', do_dump(o.producer)] + do_dump(o.producer_args) result end def dump_Multibinding o result = ['multibind', o.id] result << 'override' if o.override result << 'abstract' if o.abstract result.concat([do_dump(o.type), o.name]) result << "(in #{o.multibind_id})" if o.multibind_id result << ['to', do_dump(o.producer)] + do_dump(o.producer_args) result end def dump_Bindings o do_dump(o.bindings) end def dump_NamedBindings o result = ['named-bindings', o.name, :indent] o.bindings.each do |b| result << :break result << do_dump(b) end result << :dedent result end def dump_LayeredBindings o result = ['layers', :indent] o.layers.each do |layer| result << :break result << do_dump(layer) end result << :dedent result end def dump_ContributedBindings o ['contributed', o.name, do_dump(o.bindings)] end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/bindings_model_meta.rb������������������������������������������0000664�0052762�0001160�00000016455�12650174557�023604� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'rgen/metamodel_builder' # The Bindings model is a model of Key to Producer mappings (bindings). # The central concept is that a Bindings is a nested structure of bindings. # A top level Bindings should be a NamedBindings (the name is used primarily # in error messages). A Key is a Type/Name combination. # # TODO: In this version, references to "any object" uses the class Object, # but this is only temporary. The intent is to use specific Puppet Objects # that are typed using the Puppet Type System (to enable serialization). # # @see Puppet::Pops::Binder::BindingsFactory The BindingsFactory for more details on how to create model instances. # @api public module Puppet::Pops::Binder::Bindings extend RGen::MetamodelBuilder::ModuleExtension # This declaration is used to overcome bugs in RGen. What is really wanted is an Opaque Object # type that does not serialize its values, but such an type does not work when recreating the # meta model from a dump. # Instead, after loading the model, the generated code for type validation must be patched # FakeObject = String # @abstract # @api public class BindingsModelObject < RGen::MetamodelBuilder::MMBase abstract end # @abstract # @api public # class AbstractBinding < BindingsModelObject abstract end # An abstract producer # @abstract # @api public # class ProducerDescriptor < BindingsModelObject abstract contains_one_uni 'transformer', Puppet::Pops::Model::LambdaExpression end # All producers are singleton producers unless wrapped in a non caching producer # where each lookup produces a new instance. It is an error to have a nesting level > 1 # and to nest a NonCachingProducerDescriptor. # # @api public # class NonCachingProducerDescriptor < ProducerDescriptor contains_one_uni 'producer', ProducerDescriptor end # Produces a constant value (i.e. something of {Puppet::Pops::Types::PDataType PDataType}) # @api public # class ConstantProducerDescriptor < ProducerDescriptor # TODO: This should be a typed Puppet Object has_attr 'value', FakeObject end # Produces a value by evaluating a Puppet DSL expression. # Note that the expression is not contained as it is part of a Puppet::Pops::Model::Program. # To include the expression in the serialization, the Program it is contained in must be # contained in the same serialization. This can be achieved by containing it in the # ContributedBindings that is the top of a BindingsModel produced and given to the Injector. # # @api public # class EvaluatingProducerDescriptor < ProducerDescriptor has_one 'expression', Puppet::Pops::Model::Expression end # An InstanceProducer creates an instance of the given class # Arguments are passed to the class' `new` operator in the order they are given. # @api public # class InstanceProducerDescriptor < ProducerDescriptor # TODO: This should be a typed Puppet Object ?? has_many_attr 'arguments', FakeObject, :upperBound => -1 has_attr 'class_name', String end # A ProducerProducerDescriptor, describes that the produced instance is itself a Producer # that should be used to produce the value. # @api public # class ProducerProducerDescriptor < ProducerDescriptor contains_one_uni 'producer', ProducerDescriptor, :lowerBound => 1 end # Produces a value by looking up another key (type/name) # @api public # class LookupProducerDescriptor < ProducerDescriptor contains_one_uni 'type', Puppet::Pops::Types::PAnyType has_attr 'name', String end # Produces a value by looking up another multibound key, and then looking up # the detail using a detail_key. # This is used to produce a specific service of a given type (such as a SyntaxChecker for the syntax "json"). # @api public # class HashLookupProducerDescriptor < LookupProducerDescriptor has_attr 'key', String end # Produces a value by looking up each producer in turn. The first existing producer wins. # @api public # class FirstFoundProducerDescriptor < ProducerDescriptor contains_many_uni 'producers', LookupProducerDescriptor end # @api public # @abstract class MultibindProducerDescriptor < ProducerDescriptor abstract end # Used in a Multibind of Array type unless it has a producer. May explicitly be used as well. # @api public # class ArrayMultibindProducerDescriptor < MultibindProducerDescriptor end # Used in a Multibind of Hash type unless it has a producer. May explicitly be used as well. # @api public # class HashMultibindProducerDescriptor < MultibindProducerDescriptor end # Plays the role of "Hash[String, Object] entry" but with keys in defined order. # # @api public # class NamedArgument < BindingsModelObject has_attr 'name', String, :lowerBound => 1 has_attr 'value', FakeObject end # Binds a type/name combination to a producer. Optionally marking the bindidng as being abstract, or being an # override of another binding. Optionally, the binding defines producer arguments passed to the producer when # it is created. # # @api public class Binding < AbstractBinding contains_one_uni 'type', Puppet::Pops::Types::PAnyType has_attr 'name', String has_attr 'override', Boolean has_attr 'abstract', Boolean has_attr 'final', Boolean # If set is a contribution in a multibind has_attr 'multibind_id', String, :lowerBound => 0 # Invariant: Only multibinds may have lowerBound 0, all regular Binding must have a producer. contains_one_uni 'producer', ProducerDescriptor, :lowerBound => 0 contains_many_uni 'producer_args', NamedArgument, :lowerBound => 0 end # A multibinding is a binding other bindings can contribute to. # # @api public class Multibinding < Binding has_attr 'id', String end # A container of Binding instances # @api public # class Bindings < AbstractBinding contains_many_uni 'bindings', AbstractBinding end # The top level container of bindings can have a name (for error messages, logging, tracing). # May be nested. # @api public # class NamedBindings < Bindings has_attr 'name', String end # A named layer of bindings having the same priority. # @api public class NamedLayer < BindingsModelObject has_attr 'name', String, :lowerBound => 1 contains_many_uni 'bindings', NamedBindings end # A list of layers with bindings in descending priority order. # @api public # class LayeredBindings < BindingsModelObject contains_many_uni 'layers', NamedLayer end # ContributedBindings is a named container of one or more NamedBindings. # The intent is that a bindings producer returns a ContributedBindings that identifies the contributor # as opposed to the names of the different set of bindings. The ContributorBindings name is typically # a technical name that indicates their source (a service). # # When EvaluatingProducerDescriptor is used, it holds a reference to an Expression. That expression # should be contained in the programs referenced in the ContributedBindings that contains that producer. # While the bindings model will still work if this is not the case, it will not serialize and deserialize # correctly. # # @api public # class ContributedBindings < NamedLayer contains_many_uni 'programs', Puppet::Pops::Model::Program end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/bindings_validator_factory.rb�����������������������������������0000664�0052762�0001160�00000001316�12650174557�025200� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Configures validation suitable for the bindings model # @api public # class Puppet::Pops::Binder::BindingsValidatorFactory < Puppet::Pops::Validation::Factory Issues = Puppet::Pops::Binder::BinderIssues # Produces the checker to use def checker diagnostic_producer Puppet::Pops::Binder::BindingsChecker.new(diagnostic_producer) end # Produces the label provider to use def label_provider Puppet::Pops::Binder::BindingsLabelProvider.new() end # Produces the severity producer to use def severity_producer p = super # Configure each issue that should **not** be an error # p[Issues::MISSING_BINDINGS] = :warning p[Issues::MISSING_LAYERS] = :warning p end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/config/���������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020525� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/config/binder_config.rb�����������������������������������������0000664�0052762�0001160�00000007176�12650174557�023656� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops::Binder::Config # Class holding the Binder Configuration # The configuration is obtained from the file 'binder_config.yaml' # that must reside in the root directory of the site # @api public # class BinderConfig # The layering configuration is an array of layers from most to least significant. # Each layer is represented by a Hash containing :name and :include and optionally :exclude # # @return [Array<Hash<String, String>, Hash<String, Array<String>>] # @api public # attr_reader :layering_config # @return <Hash<String, String>] ({}) optional mapping of bindings-scheme to handler class name attr_reader :scheme_extensions # @return [String] the loaded config file attr_accessor :config_file DEFAULT_LAYERS = [ { 'name' => 'site', 'include' => [ 'confdir:/default?optional'] }, { 'name' => 'modules', 'include' => [ 'module:/*::default'] }, ] DEFAULT_SCHEME_EXTENSIONS = {} def default_config() # This is hardcoded now, but may be a user supplied default configuration later {'version' => 1, 'layers' => default_layers } end def confdir() Puppet.settings[:confdir] end # Creates a new Config. The configuration is loaded from the file 'binder_config.yaml' which # is expected to be found in confdir. # # @param diagnostics [DiagnosticProducer] collector of diagnostics # @api public # def initialize(diagnostics) @config_file = Puppet.settings[:binder_config] # if file is stated, it must exist # otherwise it is optional $confdir/binder_conf.yaml # and if that fails, the default case @config_file when NilClass # use the config file if it exists rootdir = confdir if rootdir.is_a?(String) expanded_config_file = File.expand_path(File.join(rootdir, '/binder_config.yaml')) if Puppet::FileSystem.exist?(expanded_config_file) @config_file = expanded_config_file end else raise ArgumentError, "No Puppet settings 'confdir', or it is not a String" end when String unless Puppet::FileSystem.exist?(@config_file) raise ArgumentError, "Cannot find the given binder configuration file '#{@config_file}'" end else raise ArgumentError, "The setting binder_config is expected to be a String, got: #{@config_file.class.name}." end unless @config_file.is_a?(String) && Puppet::FileSystem.exist?(@config_file) @config_file = nil # use defaults end validator = BinderConfigChecker.new(diagnostics) begin data = @config_file ? YAML.load_file(@config_file) : default_config() validator.validate(data, @config_file) rescue Errno::ENOENT diagnostics.accept(Issues::CONFIG_FILE_NOT_FOUND, @config_file) rescue Errno::ENOTDIR diagnostics.accept(Issues::CONFIG_FILE_NOT_FOUND, @config_file) rescue ::SyntaxError => e diagnostics.accept(Issues::CONFIG_FILE_SYNTAX_ERROR, @config_file, :detail => e.message) end unless diagnostics.errors? @layering_config = data['layers'] or default_layers @scheme_extensions = (data['extensions'] and data['extensions']['scheme_handlers'] or default_scheme_extensions) else @layering_config = [] @scheme_extensions = {} end end # The default_xxx methods exists to make it easier to do mocking in tests. # @api private def default_layers DEFAULT_LAYERS end # @api private def default_scheme_extensions DEFAULT_SCHEME_EXTENSIONS end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/config/binder_config_checker.rb���������������������������������0000664�0052762�0001160�00000011407�12650174557�025332� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops::Binder::Config # Validates the consistency of a Binder::BinderConfig class BinderConfigChecker # Create an instance with a diagnostic producer that will receive the result during validation # @param diagnostics [DiagnosticProducer] The producer that will receive the diagnostic # @api public # def initialize(diagnostics) @diagnostics = diagnostics t = Puppet::Pops::Types @type_calculator = t::TypeCalculator.new() @array_of_string_type = t::TypeFactory.array_of(t::TypeFactory.string()) end # Validate the consistency of the given data. Diagnostics will be emitted to the DiagnosticProducer # that was set when this checker was created # # @param data [Object] The data read from the config file # @param config_file [String] The full path of the file. Used in error messages # @api public # def validate(data, config_file) @unique_layer_names = Set.new() if data.is_a?(Hash) check_top_level(data, config_file) else accept(Issues::CONFIG_IS_NOT_HASH, config_file) end end private def accept(issue, semantic, options = {}) @diagnostics.accept(issue, semantic, options) end def check_top_level(data, config_file) if layers = (data['layers'] || data[:layers]) check_layers(layers, config_file) else accept(Issues::CONFIG_LAYERS_MISSING, config_file) end if version = (data['version'] or data[:version]) accept(Issues::CONFIG_WRONG_VERSION, config_file, {:expected => 1, :actual => version}) unless version == 1 else accept(Issues::CONFIG_VERSION_MISSING, config_file) end if extensions = data['extensions'] check_extensions(extensions, config_file) end end def check_layers(layers, config_file) unless layers.is_a?(Array) accept(Issues::LAYERS_IS_NOT_ARRAY, config_file, :klass => data.class) else layers.each {|layer| check_layer(layer, config_file) } end end def check_layer(layer, config_file) unless layer.is_a?(Hash) accept(Issues::LAYER_IS_NOT_HASH, config_file, :klass => layer.class) return end layer.each_pair do |k, v| case k when 'name' unless v.is_a?(String) accept(Issues::LAYER_NAME_NOT_STRING, config_file, :class_name => v.class.name) end unless @unique_layer_names.add?(v) accept(Issues::DUPLICATE_LAYER_NAME, config_file, :name => v.to_s ) end when 'include' check_bindings_references('include', v, config_file) when 'exclude' check_bindings_references('exclude', v, config_file) when Symbol accept(Issues::LAYER_ATTRIBUTE_IS_SYMBOL, config_file, :name => k.to_s) else accept(Issues::UNKNOWN_LAYER_ATTRIBUTE, config_file, :name => k.to_s ) end end end # references to bindings is a single String URI, or an array of String URI # @param kind [String] 'include' or 'exclude' (used in issue messages) # @param value [String, Array<String>] one or more String URI binding references # @param config_file [String] reference to the loaded config file # def check_bindings_references(kind, value, config_file) return check_reference(value, kind, config_file) if value.is_a?(String) accept(Issues::BINDINGS_REF_NOT_STRING_OR_ARRAY, config_file, :kind => kind ) unless value.is_a?(Array) value.each {|ref| check_reference(ref, kind, config_file) } end # A reference is a URI in string form having a scheme and a path (at least '/') # def check_reference(value, kind, config_file) begin uri = URI.parse(value) unless uri.scheme accept(Issues::MISSING_SCHEME, config_file, :uri => uri) end unless uri.path accept(Issues::REF_WITHOUT_PATH, config_file, :uri => uri, :kind => kind) end rescue InvalidURIError => e accept(Issues::BINDINGS_REF_INVALID_URI, config_file, :msg => e.message) end end def check_extensions(extensions, config_file) unless extensions.is_a?(Hash) accept(Issues::EXTENSIONS_NOT_HASH, config_file, :actual => extensions.class.name) return end # check known extensions extensions.each_key do |key| unless ['scheme_handlers'].include? key accept(Issues::UNKNOWN_EXTENSION, config_file, :extension => key) end end if binding_schemes = extensions['scheme_handlers'] unless binding_schemes.is_a?(Hash) accept(Issues::EXTENSION_BINDING_NOT_HASH, config_file, :extension => 'scheme_handlers', :actual => binding_schemes.class.name) end end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/config/diagnostic_producer.rb�����������������������������������0000664�0052762�0001160�00000001753�12650174557�025110� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops::Binder::Config # Generates validation diagnostics class Puppet::Pops::Binder::Config::DiagnosticProducer def initialize(acceptor) @acceptor = acceptor @severity_producer = Puppet::Pops::Validation::SeverityProducer.new end def accept(issue, semantic, arguments={}) arguments[:semantic] ||= semantic severity = severity_producer.severity(issue) @acceptor.accept(Puppet::Pops::Validation::Diagnostic.new(severity, issue, nil, nil, arguments)) end def errors?() @acceptor.errors? end def severity_producer p = @severity_producer # All are errors, if there is need to mark some as warnings... # p[Issues::XXX] = :warning # ignored because there is a default p[Puppet::Pops::Binder::Config::Issues::CONFIG_LAYERS_MISSING] = :ignore # ignored because there is a default p[Puppet::Pops::Binder::Config::Issues::CONFIG_CATEGORIES_MISSING] = :ignore p end end end ���������������������puppet-3.8.5/lib/puppet/pops/binder/config/issues.rb������������������������������������������������0000664�0052762�0001160�00000006715�12650174557�022377� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops::Binder::Config::Issues # (see Puppet::Pops::Issues#issue) def self.issue (issue_code, *args, &block) Puppet::Pops::Issues.issue(issue_code, *args, &block) end CONFIG_FILE_NOT_FOUND = issue :CONFIG_FILE_NOT_FOUND do "The binder configuration file: #{semantic} can not be found." end CONFIG_FILE_SYNTAX_ERROR = issue :CONFIG_FILE_SYNTAX_ERROR, :detail do "Syntax error in configuration file: #{detail}" end CONFIG_IS_NOT_HASH = issue :CONFIG_IS_NOT_HASH do "The configuration file '#{semantic}' has no hash at the top level" end CONFIG_LAYERS_MISSING = issue :CONFIG_LAYERS_MISSING do "The configuration file '#{semantic}' has no 'layers' entry in the top level hash" end CONFIG_VERSION_MISSING = issue :CONFIG_VERSION_MISSING do "The configuration file '#{semantic}' has no 'version' entry in the top level hash" end LAYERS_IS_NOT_ARRAY = issue :LAYERS_IS_NOT_ARRAY, :klass do "The configuration file '#{semantic}' should contain a 'layers' key with an Array value, got: #{klass.name}" end LAYER_IS_NOT_HASH = issue :LAYER_IS_NOT_HASH, :klass do "The configuration file '#{semantic}' should contain one hash per layer, got #{klass.name} instead of Hash" end DUPLICATE_LAYER_NAME = issue :DUPLICATE_LAYER_NAME, :name do "Duplicate layer '#{name}' in configuration file #{semantic}" end UNKNOWN_LAYER_ATTRIBUTE = issue :UNKNOWN_LAYER_ATTRIBUTE, :name do "Unknown layer attribute '#{name}' in configuration file #{semantic}" end BINDINGS_REF_NOT_STRING_OR_ARRAY = issue :BINDINGS_REF_NOT_STRING_OR_ARRAY, :kind do "Configuration file #{semantic} has bindings reference in '#{kind}' that is neither a String nor an Array." end MISSING_SCHEME = issue :MISSING_SCHEME, :uri do "Configuration file #{semantic} contains a bindings reference: '#{uri}' without scheme." end UNKNOWN_REF_SCHEME = issue :UNKNOWN_REF_SCHEME, :uri, :kind do "Configuration file #{semantic} contains a bindings reference: '#{kind}' => '#{uri}' with unknown scheme" end REF_WITHOUT_PATH = issue :REF_WITHOUT_PATH, :uri, :kind do "Configuration file #{semantic} contains a bindings reference: '#{kind}' => '#{uri}' without path" end BINDINGS_REF_INVALID_URI = issue :BINDINGS_REF_INVALID_URI, :msg do "Configuration file #{semantic} contains a bindings reference: '#{kind}' => invalid uri, msg: '#{msg}'" end LAYER_ATTRIBUTE_IS_SYMBOL = issue :LAYER_ATTRIBUTE_IS_SYMBOL, :name do "Configuration file #{semantic} contains a layer attribute '#{name}' that is a Symbol (should be String)" end LAYER_NAME_NOT_STRING = issue :LAYER_NAME_NOT_STRING, :class_name do "Configuration file #{semantic} contains a layer name that is not a String, got a: '#{class_name}'" end CONFIG_WRONG_VERSION = issue :CONFIG_WRONG_VERSION, :expected, :actual do "The configuration file '#{semantic}' has unsupported 'version', expected: #{expected}, but got: #{actual}." end EXTENSIONS_NOT_HASH = issue :EXTENSIONS_NOT_HASH, :actual do "The configuration file '#{semantic}' contains 'extensions', expected: Hash, but got: #{actual}." end EXTENSION_BINDING_NOT_HASH = issue :EXTENSION_BINDING_NOT_HASH, :extension, :actual do "The configuration file '#{semantic}' contains '#{extension}', expected: Hash, but got: #{actual}." end UNKNOWN_EXTENSION = issue :UNKNOWN_EXTENSION, :extension do "The configuration file '#{semantic}' contains the unknown extension: #{extension}." end end ���������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/injector.rb�����������������������������������������������������0000664�0052762�0001160�00000072637�12650174557�021442� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The injector is the "lookup service" class # # Initialization # -------------- # The injector is initialized with a configured {Puppet::Pops::Binder::Binder Binder}. The Binder instance contains a resolved set of # `key => "binding information"` that is used to setup the injector. # # Lookup # ------ # It is possible to lookup either the value, or a producer of the value. The {#lookup} method looks up a value, and the # {#lookup_producer} looks up a producer. # Both of these methods can be called with three different signatures; `lookup(key)`, `lookup(type, name)`, and `lookup(name)`, # with the corresponding calls to obtain a producer; `lookup_producer(key)`, `lookup_producer(type, name)`, and `lookup_producer(name)`. # # It is possible to pass a block to {#lookup} and {#lookup_producer}, the block is passed the result of the lookup # and the result of the block is returned as the value of the lookup. This is useful in order to provide a default value. # # @example Lookup with default value # injector.lookup('favourite_food') {|x| x.nil? ? 'bacon' : x } # # Singleton or Not # ---------------- # The lookup of a value is always based on the lookup of a producer. For *singleton producers* this means that the value is # determined by the first value lookup. Subsequent lookups via `lookup` or `lookup_producer` will produce the same instance. # # *Non singleton producers* will produce a new instance on each request for a value. For constant value producers this # means that a new deep-clone is produced for mutable objects (but not for immutable objects as this is not needed). # Custom producers should have non singleton behavior, or if this is not possible ensure that the produced result is # immutable. (The behavior if a custom producer hands out a mutable value and this is mutated is undefined). # # Custom bound producers capable of producing a series of objects when bound as a singleton means that the producer # is a singleton, not the value it produces. If such a producer is bound as non singleton, each `lookup` will get a new # producer (hence, typically, restarting the series). However, the producer returned from `lookup_producer` will not # recreate the producer on each call to `produce`; i.e. each `lookup_producer` returns a producer capable of returning # a series of objects. # # @see Puppet::Pops::Binder::Binder Binder, for details about how to bind keys to producers # @see Puppet::Pops::Binder::BindingsFactory BindingsFactory, for a convenient way to create a Binder and bindings # # Assisted Inject # --------------- # The injector supports lookup of instances of classes *even if the requested class is not explicitly bound*. # This is possible for classes that have a zero argument `initialize` method, or that has a class method called # `inject` that takes two arguments; `injector`, and `scope`. # This is useful in ruby logic as a class can then use the given injector to inject details. # An `inject` class method wins over a zero argument `initialize` in all cases. # # @example Using assisted inject # # Class with assisted inject support # class Duck # attr_reader :name, :year_of_birth # # def self.inject(injector, scope, binding, *args) # # lookup default name and year of birth, and use defaults if not present # name = injector.lookup(scope,'default-duck-name') {|x| x ? x : 'Donald Duck' } # year_of_birth = injector.lookup(scope,'default-duck-year_of_birth') {|x| x ? x : 1934 } # self.new(name, year_of_birth) # end # # def initialize(name, year_of_birth) # @name = name # @year_of_birth = year_of_birth # end # end # # injector.lookup(scope, Duck) # # Produces a Duck named 'Donald Duck' or named after the binding 'default-duck-name' (and with similar treatment of # # year_of_birth). # @see Puppet::Pops::Binder::Producers::AssistedInjectProducer AssistedInjectProducer, for more details on assisted injection # # Access to key factory and type calculator # ----------------------------------------- # It is important to use the same key factory, and type calculator as the binder. It is therefor possible to obtain # these with the methods {#key_factory}, and {#type_calculator}. # # Special support for producers # ----------------------------- # There is one method specially designed for producers. The {#get_contributions} method returns an array of all contributions # to a given *contributions key*. This key is obtained from the {#key_factory} for a given multibinding. The returned set of # contributed bindings is sorted in descending precedence order. Any conflicts, merges, etc. is performed by the multibinding # producer configured for a multibinding. # # @api public # class Puppet::Pops::Binder::Injector Producers = Puppet::Pops::Binder::Producers def self.create_from_model(layered_bindings_model) self.new(Puppet::Pops::Binder::Binder.new(layered_bindings_model)) end def self.create_from_hash(name, key_value_hash) factory = Puppet::Pops::Binder::BindingsFactory named_bindings = factory.named_bindings(name) { key_value_hash.each {|k,v| bind.name(k).to(v) }} layered_bindings = factory.layered_bindings(factory.named_layer(name+'-layer',named_bindings.model)) self.new(Puppet::Pops::Binder::Binder.new(layered_bindings)) end # Creates an injector with a single bindings layer created with the given name, and the bindings # produced by the given block. The block is evaluated with self bound to a BindingsContainerBuilder. # # @example # Injector.create('mysettings') do # bind('name').to(42) # end # # @api public # def self.create(name, &block) factory = Puppet::Pops::Binder::BindingsFactory layered_bindings = factory.layered_bindings(factory.named_layer(name+'-layer',factory.named_bindings(name, &block).model)) self.new(Puppet::Pops::Binder::Binder.new(layered_bindings)) end # Creates an overriding injector with a single bindings layer # created with the given name, and the bindings produced by the given block. # The block is evaluated with self bound to a BindingsContainerBuilder. # # @example # an_injector.override('myoverrides') do # bind('name').to(43) # end # # @api public # def override(name, &block) factory = Puppet::Pops::Binder::BindingsFactory layered_bindings = factory.layered_bindings(factory.named_layer(name+'-layer',factory.named_bindings(name, &block).model)) self.class.new(Puppet::Pops::Binder::Binder.new(layered_bindings, @impl.binder)) end # Creates an overriding injector with bindings from a bindings model (a LayeredBindings) which # may consists of multiple layers of bindings. # # @api public # def override_with_model(layered_bindings) unless layered_bindings.is_a?(Puppet::Pops::Binder::Bindings::LayeredBindings) raise ArgumentError, "Expected a LayeredBindings model, got '#{bindings_model.class}'" end self.class.new(Puppet::Pops::Binder::Binder.new(layered_bindings, @impl.binder)) end # Creates an overriding injector with a single bindings layer # created with the given name, and the bindings given in the key_value_hash # @api public # def override_with_hash(name, key_value_hash) factory = Puppet::Pops::Binder::BindingsFactory named_bindings = factory.named_bindings(name) { key_value_hash.each {|k,v| bind.name(k).to(v) }} layered_bindings = factory.layered_bindings(factory.named_layer(name+'-layer',named_bindings.model)) self.class.new(Puppet::Pops::Binder::Binder.new(layered_bindings, @impl.binder)) end # An Injector is initialized with a configured {Puppet::Pops::Binder::Binder Binder}. # # @param configured_binder [Puppet::Pops::Binder::Binder,nil] The configured binder containing effective bindings. A given value # of nil creates an injector that returns or yields nil on all lookup requests. # @raise ArgumentError if the given binder is not fully configured # # @api public # def initialize(configured_binder, parent_injector = nil) if configured_binder.nil? @impl = Private::NullInjectorImpl.new() else @impl = Private::InjectorImpl.new(configured_binder, parent_injector) end end # The KeyFactory used to produce keys in this injector. # The factory is shared with the Binder to ensure consistent translation to keys. # A compatible type calculator can also be obtained from the key factory. # @return [Puppet::Pops::Binder::KeyFactory] the key factory in use # # @api public # def key_factory() @impl.key_factory end # Returns the TypeCalculator in use for keys. The same calculator (as used for keys) should be used if there is a need # to check type conformance, or infer the type of Ruby objects. # # @return [Puppet::Pops::Types::TypeCalculator] the type calculator that is in use for keys # @api public # def type_calculator() @impl.type_calculator() end # Lookup (a.k.a "inject") of a value given a key. # The lookup may be called with different parameters. This method is a convenience method that # dispatches to one of #lookup_key or #lookup_type depending on the arguments. It also provides # the ability to use an optional block that is called with the looked up value, or scope and value if the # block takes two parameters. This is useful to provide a default value or other transformations, calculations # based on the result of the lookup. # # @overload lookup(scope, key) # (see #lookup_key) # @param scope [Puppet::Parser::Scope] the scope to use for evaluation # @param key [Object] an opaque object being the full key # # @overload lookup(scope, type, name = '') # (see #lookup_type) # @param scope [Puppet::Parser::Scope] the scope to use for evaluation # @param type [Puppet::Pops::Types::PAnyType] the type of what to lookup # @param name [String] the name to use, defaults to empty string (for unnamed) # # @overload lookup(scope, name) # Lookup of Data type with given name. # @see #lookup_type # @param scope [Puppet::Parser::Scope] the scope to use for evaluation # @param name [String] the Data/name to lookup # # @yield [value] passes the looked up value to an optional block and returns what this block returns # @yield [scope, value] passes scope and value to the block and returns what this block returns # @yieldparam scope [Puppet::Parser::Scope] the scope given to lookup # @yieldparam value [Object, nil] the looked up value or nil if nothing was found # # @raise [ArgumentError] if the block has an arity that is not 1 or 2 # # @api public # def lookup(scope, *args, &block) @impl.lookup(scope, *args, &block) end # Looks up a (typesafe) value based on a type/name combination. # Creates a key for the type/name combination using a KeyFactory. Specialization of the Data type are transformed # to a Data key, and the result is type checked to conform with the given key. # # @param type [Puppet::Pops::Types::PAnyType] the type to lookup as defined by Puppet::Pops::Types::TypeFactory # @param name [String] the (optional for non `Data` types) name of the entry to lookup. # The name may be an empty String (the default), but not nil. The name is required for lookup for subtypes of # `Data`. # @return [Object, nil] the looked up bound object, or nil if not found (type conformance with given type is guaranteed) # @raise [ArgumentError] if the produced value does not conform with the given type # # @api public # def lookup_type(scope, type, name='') @impl.lookup_type(scope, type, name) end # Looks up the key and returns the entry, or nil if no entry is found. # Produced type is checked for type conformance with its binding, but not with the lookup key. # (This since all subtypes of PDataType are looked up using a key based on PDataType). # Use the Puppet::Pops::Types::TypeCalculator#instance? method to check for conformance of the result # if this is wanted, or use #lookup_type. # # @param key [Object] lookup of key as produced by the key factory # @return [Object, nil] produced value of type that conforms with bound type (type conformance with key not guaranteed). # @raise [ArgumentError] if the produced value does not conform with the bound type # # @api public # def lookup_key(scope, key) @impl.lookup_key(scope, key) end # Lookup (a.k.a "inject") producer of a value given a key. # The producer lookup may be called with different parameters. This method is a convenience method that # dispatches to one of #lookup_producer_key or #lookup_producer_type depending on the arguments. It also provides # the ability to use an optional block that is called with the looked up producer, or scope and producer if the # block takes two parameters. This is useful to provide a default value, call a custom producer method, # or other transformations, calculations based on the result of the lookup. # # @overload lookup_producer(scope, key) # (see #lookup_proudcer_key) # @param scope [Puppet::Parser::Scope] the scope to use for evaluation # @param key [Object] an opaque object being the full key # # @overload lookup_producer(scope, type, name = '') # (see #lookup_type) # @param scope [Puppet::Parser::Scope] the scope to use for evaluation # @param type [Puppet::Pops::Types::PAnyType], the type of what to lookup # @param name [String], the name to use, defaults to empty string (for unnamed) # # @overload lookup_producer(scope, name) # Lookup of Data type with given name. # @see #lookup_type # @param scope [Puppet::Parser::Scope] the scope to use for evaluation # @param name [String], the Data/name to lookup # # @return [Puppet::Pops::Binder::Producers::Producer, Object, nil] a producer, or what the optional block returns # # @yield [producer] passes the looked up producer to an optional block and returns what this block returns # @yield [scope, producer] passes scope and producer to the block and returns what this block returns # @yieldparam producer [Puppet::Pops::Binder::Producers::Producer, nil] the looked up producer or nil if nothing was bound # @yieldparam scope [Puppet::Parser::Scope] the scope given to lookup # # @raise [ArgumentError] if the block has an arity that is not 1 or 2 # # @api public # def lookup_producer(scope, *args, &block) @impl.lookup_producer(scope, *args, &block) end # Looks up a Producer given an opaque binder key. # @return [Puppet::Pops::Binder::Producers::Producer, nil] the bound producer, or nil if no such producer was found. # # @api public # def lookup_producer_key(scope, key) @impl.lookup_producer_key(scope, key) end # Looks up a Producer given a type/name key. # @note The result is not type checked (it cannot be until the producer has produced an instance). # @return [Puppet::Pops::Binder::Producers::Producer, nil] the bound producer, or nil if no such producer was found # # @api public # def lookup_producer_type(scope, type, name='') @impl.lookup_producer_type(scope, type, name) end # Returns the contributions to a multibind given its contribution key (as produced by the KeyFactory). # This method is typically used by multibind value producers, but may be used for introspection of the injector's state. # # @param scope [Puppet::Parser::Scope] the scope to use # @param contributions_key [Object] Opaque key as produced by KeyFactory as the contributions key for a multibinding # @return [Array<Puppet::Pops::Binder::InjectorEntry>] the contributions sorted in deecending order of precedence # # @api public # def get_contributions(scope, contributions_key) @impl.get_contributions(scope, contributions_key) end # Returns an Injector that returns (or yields) nil on all lookups, and produces an empty structure for contributions # This method is intended for testing purposes. # def self.null_injector self.new(nil) end # The implementation of the Injector is private. # @see Puppet::Pops::Binder::Injector The public API this module implements. # @api private # module Private # This is a mocking "Null" implementation of Injector. It never finds anything # @api private class NullInjectorImpl attr_reader :entries attr_reader :key_factory attr_reader :type_calculator def initialize @entries = [] @key_factory = Puppet::Pops::Binder::KeyFactory.new() @type_calculator = @key_factory.type_calculator end def lookup(scope, *args, &block) raise ArgumentError, "lookup should be called with two or three arguments, got: #{args.size()+1}" unless args.size.between?(1,2) # call block with result if given if block case block.arity when 1 block.call(nil) when 2 block.call(scope, nil) else raise ArgumentError, "The block should have arity 1 or 2" end else val end end # @api private def binder nil end # @api private def lookup_key(scope, key) nil end # @api private def lookup_producer(scope, *args, &block) lookup(scope, *args, &block) end # @api private def lookup_producer_key(scope, key) nil end # @api private def lookup_producer_type(scope, type, name='') nil end def get_contributions() [] end end # @api private # class InjectorImpl # Hash of key => InjectorEntry # @api private # attr_reader :entries attr_reader :key_factory attr_reader :type_calculator attr_reader :binder def initialize(configured_binder, parent_injector = nil) @binder = configured_binder @parent = parent_injector # TODO: Different error message raise ArgumentError, "Given Binder is not configured" unless configured_binder #&& configured_binder.configured?() @entries = configured_binder.injector_entries() # It is essential that the injector uses the same key factory as the binder since keys must be # represented the same (but still opaque) way. # @key_factory = configured_binder.key_factory() @type_calculator = key_factory.type_calculator() @@transform_visitor ||= Puppet::Pops::Visitor.new(nil,"transform", 2, 2) @recursion_lock = [ ] end # @api private def lookup(scope, *args, &block) raise ArgumentError, "lookup should be called with two or three arguments, got: #{args.size()+1}" unless args.size.between?(1,2) val = case args[ 0 ] when Puppet::Pops::Types::PAnyType lookup_type(scope, *args) when String raise ArgumentError, "lookup of name should only pass the name" unless args.size == 1 lookup_key(scope, key_factory.data_key(args[ 0 ])) else raise ArgumentError, 'lookup using a key should only pass a single key' unless args.size == 1 lookup_key(scope, args[ 0 ]) end # call block with result if given if block case block.arity when 1 block.call(val) when 2 block.call(scope, val) else raise ArgumentError, "The block should have arity 1 or 2" end else val end end # Produces a key for a type/name combination. # @api private def named_key(type, name) key_factory.named_key(type, name) end # Produces a key for a PDataType/name combination # @api private def data_key(name) key_factory.data_key(name) end # @api private def lookup_type(scope, type, name='') val = lookup_key(scope, named_key(type, name)) return nil if val.nil? unless key_factory.type_calculator.instance?(type, val) raise ArgumentError, "Type error: incompatible type, #{type_error_detail(type, val)}" end val end # @api private def type_error_detail(expected, actual) actual_t = type_calculator.infer(actual) "expected: #{type_calculator.string(expected)}, got: #{type_calculator.string(actual_t)}" end # @api private def lookup_key(scope, key) if @recursion_lock.include?(key) raise ArgumentError, "Lookup loop detected for key: #{key}" end begin @recursion_lock.push(key) case entry = get_entry(key) when NilClass @parent ? @parent.lookup_key(scope, key) : nil when Puppet::Pops::Binder::InjectorEntry val = produce(scope, entry) return nil if val.nil? unless key_factory.type_calculator.instance?(entry.binding.type, val) raise "Type error: incompatible type returned by producer, #{type_error_detail(entry.binding.type, val)}" end val when Producers::AssistedInjectProducer entry.produce(scope) else # internal, direct entries entry end ensure @recursion_lock.pop() end end # Should be used to get entries as it converts missing entries to NotFound entries or AssistedInject entries # # @api private def get_entry(key) case entry = entries[ key ] when NilClass # not found, is this an assisted inject? if clazz = assistable_injected_class(key) entry = Producers::AssistedInjectProducer.new(self, clazz) entries[ key ] = entry else entries[ key ] = NotFound.new() entry = nil end when NotFound entry = nil end entry end # Returns contributions to a multibind in precedence order; highest first. # Returns an Array on the form [ [key, entry], [key, entry]] where the key is intended to be used to lookup the value # (or a producer) for that entry. # @api private def get_contributions(scope, contributions_key) result = {} return [] unless contributions = lookup_key(scope, contributions_key) contributions.each { |k| result[k] = get_entry(k) } result.sort {|a, b| a[0] <=> b[0] } #result.sort_by {|key, entry| entry } end # Produces an injectable class given a key, or nil if key does not represent an injectable class # @api private # def assistable_injected_class(key) kt = key_factory.get_type(key) return nil unless kt.is_a?(Puppet::Pops::Types::PRuntimeType) && kt.runtime == :ruby && !key_factory.is_named?(key) type_calculator.injectable_class(kt) end def lookup_producer(scope, *args, &block) raise ArgumentError, "lookup_producer should be called with two or three arguments, got: #{args.size()+1}" unless args.size <= 2 p = case args[ 0 ] when Puppet::Pops::Types::PAnyType lookup_producer_type(scope, *args) when String raise ArgumentError, "lookup_producer of name should only pass the name" unless args.size == 1 lookup_producer_key(scope, key_factory.data_key(args[ 0 ])) else raise ArgumentError, "lookup_producer using a key should only pass a single key" unless args.size == 1 lookup_producer_key(scope, args[ 0 ]) end # call block with result if given if block case block.arity when 1 block.call(p) when 2 block.call(scope, p) else raise ArgumentError, "The block should have arity 1 or 2" end else p end end # @api private def lookup_producer_key(scope, key) if @recursion_lock.include?(key) raise ArgumentError, "Lookup loop detected for key: #{key}" end begin @recursion_lock.push(key) producer(scope, get_entry(key), :multiple_use) ensure @recursion_lock.pop() end end # @api private def lookup_producer_type(scope, type, name='') lookup_producer_key(scope, named_key(type, name)) end # Returns the producer for the entry # @return [Puppet::Pops::Binder::Producers::Producer] the entry's producer. # # @api private # def producer(scope, entry, use) return nil unless entry # not found return entry.producer(scope) if entry.is_a?(Producers::AssistedInjectProducer) unless entry.cached_producer entry.cached_producer = transform(entry.binding.producer, scope, entry) end unless entry.cached_producer raise ArgumentError, "Injector entry without a producer #{format_binding(entry.binding)}" end entry.cached_producer.producer(scope) end # @api private def transform(producer_descriptor, scope, entry) @@transform_visitor.visit_this_2(self, producer_descriptor, scope, entry) end # Returns the produced instance # @return [Object] the produced instance # @api private # def produce(scope, entry) return nil unless entry # not found producer(scope, entry, :single_use).produce(scope) end # @api private def named_arguments_to_hash(named_args) nb = named_args.nil? ? [] : named_args result = {} nb.each {|arg| result[ :"#{arg.name}" ] = arg.value } result end # @api private def merge_producer_options(binding, options) named_arguments_to_hash(binding.producer_args).merge(options) end # @api private def format_binding(b) Puppet::Pops::Binder::Binder.format_binding(b) end # Handles a missing producer (which is valid for a Multibinding where one is selected automatically) # @api private # def transform_NilClass(descriptor, scope, entry) unless entry.binding.is_a?(Puppet::Pops::Binder::Bindings::Multibinding) raise ArgumentError, "Binding without producer detected, #{format_binding(entry.binding)}" end case entry.binding.type when Puppet::Pops::Types::PArrayType transform(Puppet::Pops::Binder::Bindings::ArrayMultibindProducerDescriptor.new(), scope, entry) when Puppet::Pops::Types::PHashType transform(Puppet::Pops::Binder::Bindings::HashMultibindProducerDescriptor.new(), scope, entry) else raise ArgumentError, "Unsupported multibind type, must be an array or hash type, #{format_binding(entry.binding)}" end end # @api private def transform_ArrayMultibindProducerDescriptor(descriptor, scope, entry) make_producer(Producers::ArrayMultibindProducer, descriptor, scope, entry, named_arguments_to_hash(entry.binding.producer_args)) end # @api private def transform_HashMultibindProducerDescriptor(descriptor, scope, entry) make_producer(Producers::HashMultibindProducer, descriptor, scope, entry, named_arguments_to_hash(entry.binding.producer_args)) end # @api private def transform_ConstantProducerDescriptor(descriptor, scope, entry) producer_class = singleton?(descriptor) ? Producers::SingletonProducer : Producers::DeepCloningProducer producer_class.new(self, entry.binding, scope, merge_producer_options(entry.binding, {:value => descriptor.value})) end # @api private def transform_InstanceProducerDescriptor(descriptor, scope, entry) make_producer(Producers::InstantiatingProducer, descriptor, scope, entry, merge_producer_options(entry.binding, {:class_name => descriptor.class_name, :init_args => descriptor.arguments})) end # @api private def transform_EvaluatingProducerDescriptor(descriptor, scope, entry) make_producer(Producers::EvaluatingProducer, descriptor, scope, entry, merge_producer_options(entry.binding, {:expression => descriptor.expression})) end # @api private def make_producer(clazz, descriptor, scope, entry, options) singleton_wrapped(descriptor, scope, entry, clazz.new(self, entry.binding, scope, options)) end # @api private def singleton_wrapped(descriptor, scope, entry, producer) return producer unless singleton?(descriptor) Producers::SingletonProducer.new(self, entry.binding, scope, merge_producer_options(entry.binding, {:value => producer.produce(scope)})) end # @api private def transform_ProducerProducerDescriptor(descriptor, scope, entry) p = transform(descriptor.producer, scope, entry) clazz = singleton?(descriptor) ? Producers::SingletonProducerProducer : Producers::ProducerProducer clazz.new(self, entry.binding, scope, merge_producer_options(entry.binding, merge_producer_options(entry.binding, { :producer_producer => p }))) end # @api private def transform_LookupProducerDescriptor(descriptor, scope, entry) make_producer(Producers::LookupProducer, descriptor, scope, entry, merge_producer_options(entry.binding, {:type => descriptor.type, :name => descriptor.name})) end # @api private def transform_HashLookupProducerDescriptor(descriptor, scope, entry) make_producer(Producers::LookupKeyProducer, descriptor, scope, entry, merge_producer_options(entry.binding, {:type => descriptor.type, :name => descriptor.name, :key => descriptor.key})) end # @api private def transform_NonCachingProducerDescriptor(descriptor, scope, entry) # simply delegates to the wrapped producer transform(descriptor.producer, scope, entry) end # @api private def transform_FirstFoundProducerDescriptor(descriptor, scope, entry) make_producer(Producers::FirstFoundProducer, descriptor, scope, entry, merge_producer_options(entry.binding, {:producers => descriptor.producers.collect {|p| transform(p, scope, entry) }})) end # @api private def singleton?(descriptor) ! descriptor.eContainer().is_a?(Puppet::Pops::Binder::Bindings::NonCachingProducerDescriptor) end # Special marker class used in entries # @api private class NotFound end end end end �������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/injector_entry.rb�����������������������������������������������0000664�0052762�0001160�00000002567�12650174557�022656� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Represents an entry in the injectors internal data. # # @api public # class Puppet::Pops::Binder::InjectorEntry # @return [Object] An opaque (comparable) object representing the precedence # @api public attr_reader :precedence # @return [Puppet::Pops::Binder::Bindings::Binding] The binding for this entry # @api public attr_reader :binding # @api private attr_accessor :resolved # @api private attr_accessor :cached_producer # @api private def initialize(binding, precedence = 0) @precedence = precedence.freeze @binding = binding @cached_producer = nil end # Marks an overriding entry as resolved (if not an overriding entry, the marking has no effect). # @api private # def mark_override_resolved() @resolved = true end # The binding is resolved if it is non-override, or if the override has been resolved # @api private # def is_resolved?() !binding.override || resolved end def is_abstract? binding.abstract end def is_final? binding.final end # Compares against another InjectorEntry by comparing precedence. # @param injector_entry [InjectorEntry] entry to compare against. # @return [Integer] 1, if this entry has higher precedence, 0 if equal, and -1 if given entry has higher precedence. # @api public # def <=> (injector_entry) precedence <=> injector_entry.precedence end end �����������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/key_factory.rb��������������������������������������������������0000664�0052762�0001160�00000003056�12650174557�022131� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The KeyFactory is responsible for creating keys used for lookup of bindings. # @api public # class Puppet::Pops::Binder::KeyFactory attr_reader :type_calculator # @api public def initialize(type_calculator = Puppet::Pops::Types::TypeCalculator.new()) @type_calculator = type_calculator end # @api public def binding_key(binding) named_key(binding.type, binding.name) end # @api public def named_key(type, name) [(@type_calculator.assignable?(@type_calculator.data, type) ? @type_calculator.data : type), name] end # @api public def data_key(name) [@type_calculator.data, name] end # @api public def is_contributions_key?(s) return false unless s.is_a?(String) s.start_with?('mc_') end # @api public def multibind_contributions(multibind_id) "mc_#{multibind_id}" end # @api public def multibind_contribution_key_to_id(contributions_key) # removes the leading "mc_" from the key to get the multibind_id contributions_key[3..-1] end # @api public def is_named?(key) key.is_a?(Array) && key[1] && !key[1].empty? end # @api public def is_data?(key) return false unless key.is_a?(Array) && key[0].is_a?(Puppet::Pops::Types::PAnyType) type_calculator.assignable?(type_calculator.data(), key[0]) end # @api public def is_ruby?(key) key.is_a?(Array) && key[0].is_a?(Puppet::Pops::Types::PRuntimeType) && key[0].runtime == :ruby end # Returns the type of the key # @api public # def get_type(key) return nil unless key.is_a?(Array) key[0] end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/lookup.rb�������������������������������������������������������0000664�0052762�0001160�00000016004�12650174557�021120� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This class is the backing implementation of the Puppet function 'lookup'. # See puppet/parser/functions/lookup.rb for documentation. # class Puppet::Pops::Binder::Lookup def self.parse_lookup_args(args) options = {} pblock = if args[-1].respond_to?(:puppet_lambda) args.pop end case args.size when 1 # name, or all options if args[ 0 ].is_a?(Hash) options = to_symbolic_hash(args[ 0 ]) else options[ :name ] = args[ 0 ] end when 2 # name and type, or name and options if args[ 1 ].is_a?(Hash) options = to_symbolic_hash(args[ 1 ]) options[:name] = args[ 0 ] # silently overwrite option with given name else options[:name] = args[ 0 ] options[:type] = args[ 1 ] end when 3 # name, type, default (no options) options[ :name ] = args[ 0 ] options[ :type ] = args[ 1 ] options[ :default ] = args[ 2 ] else raise Puppet::ParseError, "The lookup function accepts 1-3 arguments, got #{args.size}" end options[:pblock] = pblock options end def self.to_symbolic_hash(input) names = [:name, :type, :default, :accept_undef, :extra, :override] options = {} names.each {|n| options[n] = undef_as_nil(input[n.to_s] || input[n]) } options end private_class_method :to_symbolic_hash def self.type_mismatch(type_calculator, expected, got) "has wrong type, expected #{type_calculator.string(expected)}, got #{type_calculator.string(got)}" end private_class_method :type_mismatch def self.fail(msg) raise Puppet::ParseError, "Function lookup() " + msg end private_class_method :fail def self.fail_lookup(names) name_part = if names.size == 1 "the name '#{names[0]}'" else "any of the names ['" + names.join(', ') + "']" end fail("did not find a value for #{name_part}") end private_class_method :fail_lookup def self.validate_options(options, type_calculator) type_parser = Puppet::Pops::Types::TypeParser.new name_type = type_parser.parse('Variant[Array[String], String]') if is_nil_or_undef?(options[:name]) || options[:name].is_a?(Array) && options[:name].empty? fail ("requires a name, or array of names. Got nothing to lookup.") end t = type_calculator.infer(options[:name]) if ! type_calculator.assignable?(name_type, t) fail("given 'name' argument, #{type_mismatch(type_calculator, name_type, t)}") end # unless a type is already given (future case), parse the type (or default 'Data'), fails if invalid type is given unless options[:type].is_a?(Puppet::Pops::Types::PAnyType) options[:type] = type_parser.parse(options[:type] || 'Data') end # default value must comply with the given type if options[:default] t = type_calculator.infer(options[:default]) if ! type_calculator.assignable?(options[:type], t) fail("'default' value #{type_mismatch(type_calculator, options[:type], t)}") end end if options[:extra] && !options[:extra].is_a?(Hash) # do not perform inference here, it is enough to know that it is not a hash fail("'extra' value must be a Hash, got #{options[:extra].class}") end options[:extra] = {} unless options[:extra] if options[:override] && !options[:override].is_a?(Hash) # do not perform inference here, it is enough to know that it is not a hash fail("'override' value must be a Hash, got #{options[:extra].class}") end options[:override] = {} unless options[:override] end private_class_method :validate_options def self.nil_as_undef(x) x.nil? ? :undef : x end private_class_method :nil_as_undef def self.undef_as_nil(x) is_nil_or_undef?(x) ? nil : x end private_class_method :undef_as_nil def self.is_nil_or_undef?(x) x.nil? || x == :undef end private_class_method :is_nil_or_undef? # This is used as a marker - a value that cannot (at least not easily) by mistake be found in # hiera data. # class PrivateNotFoundMarker; end def self.search_for(scope, type, name, options) # search in order, override, injector, hiera, then extra if !(result = options[:override][name]).nil? result elsif !(result = scope.compiler.injector.lookup(scope, type, name)).nil? result else result = call_hiera_function(scope, name, PrivateNotFoundMarker) if !result.nil? && result != PrivateNotFoundMarker result else options[:extra][name] end end end private_class_method :search_for def self.call_hiera_function(scope, name, dflt) loader = scope.compiler.loaders.private_environment_loader func = loader.load(:function, :hiera) unless loader.nil? raise Error, 'Function not found: hiera' if func.nil? func.call(scope, name, dflt) end private_class_method :call_hiera_function # This is the method called from the puppet/parser/functions/lookup.rb # @param args [Array] array following the puppet function call conventions def self.lookup(scope, args) type_calculator = Puppet::Pops::Types::TypeCalculator.new options = parse_lookup_args(args) validate_options(options, type_calculator) names = [options[:name]].flatten type = options[:type] result_with_name = names.reduce([]) do |memo, name| break memo if !memo[1].nil? [name, search_for(scope, type, name, options)] end result = if result_with_name[1].nil? # not found, use default (which may be nil), the default is already type checked options[:default] else # injector.lookup is type-safe already do no need to type check the result result_with_name[1] end # If a block is given it is called with :undef passed as 'nil' since the lookup function # is available from 3x with --binder turned on, and the evaluation is always 4x. # TODO PUPPET4: Simply pass the value # result = if pblock = options[:pblock] result2 = case pblock.parameter_count when 1 pblock.call(undef_as_nil(result)) when 2 pblock.call(result_with_name[ 0 ], undef_as_nil(result)) else pblock.call(result_with_name[ 0 ], undef_as_nil(result), undef_as_nil(options[ :default ])) end # if the given result was returned, there is no need to type-check it again if !result2.equal?(result) t = type_calculator.infer(undef_as_nil(result2)) if !type_calculator.assignable?(type, t) fail "the value produced by the given code block #{type_mismatch(type_calculator, type, t)}" end end result2 else result end # Finally, the result if nil must be acceptable or an error is raised if is_nil_or_undef?(result) && !options[:accept_undef] fail_lookup(names) else # Since the function may be used without future parser being in effect, nil is not handled in a good # way, and should instead be turned into :undef. # TODO PUPPET4: Simply return the result # Puppet.future_parser? ? result : nil_as_undef(result) end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/producers.rb����������������������������������������������������0000664�0052762�0001160�00000100132�12650174557�021611� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This module contains the various producers used by Puppet Bindings. # The main (abstract) class is {Puppet::Pops::Binder::Producers::Producer} which documents the # Producer API and serves as a base class for all other producers. # It is required that custom producers inherit from this producer (directly or indirectly). # # The selection of a Producer is typically performed by the Innjector when it configures itself # from a Bindings model where a {Puppet::Pops::Binder::Bindings::ProducerDescriptor} describes # which producer to use. The configuration uses this to create the concrete producer. # It is possible to describe that a particular producer class is to be used, and also to describe that # a custom producer (derived from Producer) should be used. This is available for both regular # bindings as well as multi-bindings. # # # @api public # module Puppet::Pops::Binder::Producers # Producer is an abstract base class representing the base contract for a bound producer. # Typically, when a lookup is performed it is the value that is returned (via a producer), but # it is also possible to lookup the producer, and ask it to produce the value (the producer may # return a series of values, which makes this especially useful). # # When looking up a producer, it is of importance to only use the API of the Producer class # unless it is known that a particular custom producer class has been bound. # # Custom Producers # ---------------- # The intent is that this class is derived for custom producers that require additional # options/arguments when producing an instance. Such a custom producer may raise an error if called # with too few arguments, or may implement specific `produce` methods and always raise an # error on #produce indicating that this producer requires custom calls and that it can not # be used as an implicit producer. # # Features of Producer # -------------------- # The Producer class is abstract, but offers the ability to transform the produced result # by passing the option `:transformer` which should be a Puppet Lambda Expression taking one argument # and producing the transformed (wanted) result. # # @abstract # @api public # class Producer # A Puppet 3 AST Lambda Expression # @api public # attr_reader :transformer # Creates a Producer. # Derived classes should call this constructor to get support for transformer lambda. # # @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates # @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer # @param scope [Puppet::Parser::Scope] The scope to use for evaluation # @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value # @api public # def initialize(injector, binding, scope, options) if transformer_lambda = options[:transformer] if transformer_lambda.is_a?(Proc) raise ArgumentError, "Transformer Proc must take one argument; value." unless transformer_lambda.arity == 1 @transformer = transformer_lambda else raise ArgumentError, "Transformer must be a LambdaExpression" unless transformer_lambda.is_a?(Puppet::Pops::Model::LambdaExpression) raise ArgumentError, "Transformer lambda must take one argument; value." unless transformer_lambda.parameters.size() == 1 @transformer = Puppet::Pops::Parser::EvaluatingParser.new.closure(transformer_lambda, scope) end end end # Produces an instance. # @param scope [Puppet::Parser:Scope] the scope to use for evaluation # @param args [Object] arguments to custom producers, always empty for implicit productions # @return [Object] the produced instance (should never be nil). # @api public # def produce(scope, *args) do_transformation(scope, internal_produce(scope)) end # Returns the producer after possibly having recreated an internal/wrapped producer. # This implementation returns `self`. A derived class may want to override this method # to perform initialization/refresh of its internal state. This method is called when # a producer is requested. # @see Puppet::Pops::Binder::ProducerProducer for an example of implementation. # @param scope [Puppet::Parser:Scope] the scope to use for evaluation # @return [Puppet::Pops::Binder::Producer] the producer to use # @api public # def producer(scope) self end protected # Derived classes should implement this method to do the production of a value # @param scope [Puppet::Parser::Scope] the scope to use when performing lookup and evaluation # @raise [NotImplementedError] this implementation always raises an error # @abstract # @api private # def internal_produce(scope) raise NotImplementedError, "Producer-class '#{self.class.name}' should implement #internal_produce(scope)" end # Transforms the produced value if a transformer has been defined. # @param scope [Puppet::Parser::Scope] the scope used for evaluation # @param produced_value [Object, nil] the produced value (possibly nil) # @return [Object] the transformed value if a transformer is defined, else the given `produced_value` # @api private # def do_transformation(scope, produced_value) return produced_value unless transformer transformer.call(produced_value) end end # Abstract Producer holding a value # @abstract # @api public # class AbstractValueProducer < Producer # @api public attr_reader :value # @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates # @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer # @param scope [Puppet::Parser::Scope] The scope to use for evaluation # @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value # @option options [Puppet::Pops::Model::LambdaExpression, nil] :value (nil) the value to produce # @api public # def initialize(injector, binding, scope, options) super # nil is ok here, as an abstract value producer may be used to signal "not found" @value = options[:value] end end # Produces the same/singleton value on each production # @api public # class SingletonProducer < AbstractValueProducer protected # @api private def internal_produce(scope) value() end end # Produces a deep clone of its value on each production. # @api public # class DeepCloningProducer < AbstractValueProducer protected # @api private def internal_produce(scope) case value when Integer, Float, TrueClass, FalseClass, Symbol # These are immutable return value when String # ok if frozen, else fall through to default return value() if value.frozen? end # The default: serialize/deserialize to get a deep copy Marshal.load(Marshal.dump(value())) end end # This abstract producer class remembers the injector and binding. # @abstract # @api public # class AbstractArgumentedProducer < Producer # @api public attr_reader :injector # @api public attr_reader :binding # @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates # @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer # @param scope [Puppet::Parser::Scope] The scope to use for evaluation # @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value # @api public # def initialize(injector, binding, scope, options) super @injector = injector @binding = binding end end # @api public class InstantiatingProducer < AbstractArgumentedProducer # @api public attr_reader :the_class # @api public attr_reader :init_args # @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates # @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer # @param scope [Puppet::Parser::Scope] The scope to use for evaluation # @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value # @option options [String] :class_name The name of the class to create instance of # @option options [Array<Object>] :init_args ([]) Optional arguments to class constructor # @api public # def initialize(injector, binding, scope, options) # Better do this, even if a transformation of a created instance is kind of an odd thing to do, one can imagine # sending it to a function for further detailing. # super class_name = options[:class_name] raise ArgumentError, "Option 'class_name' must be given for an InstantiatingProducer" unless class_name # get class by name @the_class = Puppet::Pops::Types::ClassLoader.provide(class_name) @init_args = options[:init_args] || [] raise ArgumentError, "Can not load the class #{class_name} specified in binding named: '#{binding.name}'" unless @the_class end protected # Performs initialization the same way as Assisted Inject does (but handle arguments to # constructor) # @api private # def internal_produce(scope) result = nil # A class :inject method wins over an instance :initialize if it is present, unless a more specific # constructor exists. (i.e do not pick :inject from superclass if class has a constructor). # if the_class.respond_to?(:inject) inject_method = the_class.method(:inject) initialize_method = the_class.instance_method(:initialize) if inject_method.owner <= initialize_method.owner result = the_class.inject(injector, scope, binding, *init_args) end end if result.nil? result = the_class.new(*init_args) end result end end # @api public class FirstFoundProducer < Producer # @api public attr_reader :producers # @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates # @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer # @param scope [Puppet::Parser::Scope] The scope to use for evaluation # @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value # @option options [Array<Puppet::Pops::Binder::Producers::Producer>] :producers list of producers to consult. Required. # @api public # def initialize(injector, binding, scope, options) super @producers = options[:producers] raise ArgumentError, "Option :producers' must be set to a list of producers." if @producers.nil? raise ArgumentError, "Given 'producers' option is not an Array" unless @producers.is_a?(Array) end protected # @api private def internal_produce(scope) # return the first produced value that is non-nil (unfortunately there is no such enumerable method) producers.reduce(nil) {|memo, p| break memo unless memo.nil?; p.produce(scope)} end end # Evaluates a Puppet Expression and returns the result. # This is typically used for strings with interpolated expressions. # @api public # class EvaluatingProducer < Producer # A Puppet 3 AST Expression # @api public # attr_reader :expression # @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates # @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer # @param scope [Puppet::Parser::Scope] The scope to use for evaluation # @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value # @option options [Array<Puppet::Pops::Model::Expression>] :expression The expression to evaluate # @api public # def initialize(injector, binding, scope, options) super @expression = options[:expression] raise ArgumentError, "Option 'expression' must be given to an EvaluatingProducer." unless @expression end # @api private def internal_produce(scope) Puppet::Pops::Parser::EvaluatingParser.new.evaluate(scope, expression) end end # @api public class LookupProducer < AbstractArgumentedProducer # @api public attr_reader :type # @api public attr_reader :name # @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates # @param binder [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer # @param scope [Puppet::Parser::Scope] The scope to use for evaluation # @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value # @option options [Puppet::Pops::Types::PAnyType] :type The type to lookup # @option options [String] :name ('') The name to lookup # @api public # def initialize(injector, binder, scope, options) super @type = options[:type] @name = options[:name] || '' raise ArgumentError, "Option 'type' must be given in a LookupProducer." unless @type end protected # @api private def internal_produce(scope) injector.lookup_type(scope, type, name) end end # @api public class LookupKeyProducer < LookupProducer # @api public attr_reader :key # @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates # @param binder [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer # @param scope [Puppet::Parser::Scope] The scope to use for evaluation # @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value # @option options [Puppet::Pops::Types::PAnyType] :type The type to lookup # @option options [String] :name ('') The name to lookup # @option options [Puppet::Pops::Types::PAnyType] :key The key to lookup in the hash # @api public # def initialize(injector, binder, scope, options) super @key = options[:key] raise ArgumentError, "Option 'key' must be given in a LookupKeyProducer." if key.nil? end protected # @api private def internal_produce(scope) result = super result.is_a?(Hash) ? result[key] : nil end end # Produces the given producer, then uses that producer. # @see ProducerProducer for the non singleton version # @api public # class SingletonProducerProducer < Producer # @api public attr_reader :value_producer # @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates # @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer # @param scope [Puppet::Parser::Scope] The scope to use for evaluation # @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value # @option options [Puppet::Pops::Model::LambdaExpression] :producer_producer a producer of a value producer (required) # @api public # def initialize(injector, binding, scope, options) super p = options[:producer_producer] raise ArgumentError, "Option :producer_producer must be given in a SingletonProducerProducer" unless p @value_producer = p.produce(scope) end protected # @api private def internal_produce(scope) value_producer.produce(scope) end end # A ProducerProducer creates a producer via another producer, and then uses this created producer # to produce values. This is useful for custom production of series of values. # On each request for a producer, this producer will reset its internal producer (i.e. restarting # the series). # # @param producer_producer [#produce(scope)] the producer of the producer # # @api public # class ProducerProducer < Producer # @api public attr_reader :producer_producer # @api public attr_reader :value_producer # Creates new ProducerProducer given a producer. # # @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates # @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer # @param scope [Puppet::Parser::Scope] The scope to use for evaluation # @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value # @option options [Puppet::Pops::Binder::Producer] :producer_producer a producer of a value producer (required) # # @api public # def initialize(injector, binding, scope, options) super unless producer_producer = options[:producer_producer] raise ArgumentError, "The option :producer_producer must be set in a ProducerProducer" end raise ArgumentError, "Argument must be a Producer" unless producer_producer.is_a?(Producer) @producer_producer = producer_producer @value_producer = nil end # Updates the internal state to use a new instance of the wrapped producer. # @api public # def producer(scope) @value_producer = @producer_producer.produce(scope) self end protected # Produces a value after having created an instance of the wrapped producer (if not already created). # @api private # def internal_produce(scope, *args) producer() unless value_producer value_producer.produce(scope) end end # This type of producer should only be created by the Injector. # # @api private # class AssistedInjectProducer < Producer # An Assisted Inject Producer is created when a lookup is made of a type that is # not bound. It does not support a transformer lambda. # @note This initializer has a different signature than all others. Do not use in regular logic. # @api private # def initialize(injector, clazz) raise ArgumentError, "class must be given" unless clazz.is_a?(Class) @injector = injector @clazz = clazz @inst = nil end def produce(scope, *args) producer(scope, *args) unless @inst @inst end # @api private def producer(scope, *args) @inst = nil # A class :inject method wins over an instance :initialize if it is present, unless a more specific zero args # constructor exists. (i.e do not pick :inject from superclass if class has a zero args constructor). # if @clazz.respond_to?(:inject) inject_method = @clazz.method(:inject) initialize_method = @clazz.instance_method(:initialize) if inject_method.owner <= initialize_method.owner || initialize_method.arity != 0 @inst = @clazz.inject(@injector, scope, nil, *args) end end if @inst.nil? unless args.empty? raise ArgumentError, "Assisted Inject can not pass arguments to no-args constructor when there is no class inject method." end @inst = @clazz.new() end self end end # Abstract base class for multibind producers. # Is suitable as base class for custom implementations of multibind producers. # @abstract # @api public # class MultibindProducer < AbstractArgumentedProducer attr_reader :contributions_key # @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates # @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer # @param scope [Puppet::Parser::Scope] The scope to use for evaluation # @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value # # @api public # def initialize(injector, binding, scope, options) super @contributions_key = injector.key_factory.multibind_contributions(binding.id) end # @param expected [Array<Puppet::Pops::Types::PAnyType>, Puppet::Pops::Types::PAnyType] expected type or types # @param actual [Object, Puppet::Pops::Types::PAnyType> the actual value (or its type) # @return [String] a formatted string for inclusion as detail in an error message # @api private # def type_error_detail(expected, actual) tc = injector.type_calculator expected = [expected] unless expected.is_a?(Array) actual_t = tc.is_ptype?(actual) ? actual : tc.infer(actual) expstrs = expected.collect {|t| tc.string(t) } "expected: #{expstrs.join(', or ')}, got: #{tc.string(actual_t)}" end end # A configurable multibind producer for Array type multibindings. # # This implementation collects all contributions to the multibind and then combines them using the following rules: # # - all *unnamed* entries are added unless the option `:priority_on_unnamed` is set to true, in which case the unnamed # contribution with the highest priority is added, and the rest are ignored (unless they have the same priority in which # case an error is raised). # - all *named* entries are handled the same way as *unnamed* but the option `:priority_on_named` controls their handling. # - the option `:uniq` post processes the result to only contain unique entries # - the option `:flatten` post processes the result by flattening all nested arrays. # - If both `:flatten` and `:uniq` are true, flattening is done first. # # @note # Collection accepts elements that comply with the array's element type, or the entire type (i.e. Array[element_type]). # If the type is restrictive - e.g. Array[String] and an Array[String] is contributed, the result will not be type # compliant without also using the `:flatten` option, and a type error will be raised. For an array with relaxed typing # i.e. Array[Data], it is valid to produce a result such as `['a', ['b', 'c'], 'd']` and no flattening is required # and no error is raised (but using the array needs to be aware of potential array, non-array entries. # The use of the option `:flatten` controls how the result is flattened. # # @api public # class ArrayMultibindProducer < MultibindProducer # @return [Boolean] whether the result should be made contain unique (non-equal) entries or not # @api public attr_reader :uniq # @return [Boolean, Integer] If result should be flattened (true), or not (false), or flattened to given level (0 = none, -1 = all) # @api public attr_reader :flatten # @return [Boolean] whether priority should be considered for named contributions # @api public attr_reader :priority_on_named # @return [Boolean] whether priority should be considered for unnamed contributions # @api public attr_reader :priority_on_unnamed # @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates # @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer # @param scope [Puppet::Parser::Scope] The scope to use for evaluation # @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value # @option options [Boolean] :uniq (false) if collected result should be post-processed to contain only unique entries # @option options [Boolean, Integer] :flatten (false) if collected result should be post-processed so all contained arrays # are flattened. May be set to an Integer value to indicate the level of recursion (-1 is endless, 0 is none). # @option options [Boolean] :priority_on_named (true) if highest precedented named element should win or if all should be included # @option options [Boolean] :priority_on_unnamed (false) if highest precedented unnamed element should win or if all should be included # @api public # def initialize(injector, binding, scope, options) super @uniq = !!options[:uniq] @flatten = options[:flatten] @priority_on_named = options[:priority_on_named].nil? ? true : options[:priority_on_name] @priority_on_unnamed = !!options[:priority_on_unnamed] case @flatten when Integer when true @flatten = -1 when false @flatten = nil when NilClass @flatten = nil else raise ArgumentError, "Option :flatten must be nil, Boolean, or an integer value" unless @flatten.is_a?(Integer) end end protected # @api private def internal_produce(scope) seen = {} included_keys = [] injector.get_contributions(scope, contributions_key).each do |element| key = element[0] entry = element[1] name = entry.binding.name existing = seen[name] empty_name = name.nil? || name.empty? if existing if empty_name && priority_on_unnamed if (seen[name] <=> entry) >= 0 raise ArgumentError, "Duplicate key (same priority) contributed to Array Multibinding '#{binding.name}' with unnamed entry." end next elsif !empty_name && priority_on_named if (seen[name] <=> entry) >= 0 raise ArgumentError, "Duplicate key (same priority) contributed to Array Multibinding '#{binding.name}', key: '#{name}'." end next end else seen[name] = entry end included_keys << key end result = included_keys.collect do |k| x = injector.lookup_key(scope, k) assert_type(binding(), injector.type_calculator(), x) x end result.flatten!(flatten) if flatten result.uniq! if uniq result end # @api private def assert_type(binding, tc, value) infered = tc.infer(value) unless tc.assignable?(binding.type.element_type, infered) || tc.assignable?(binding.type, infered) raise ArgumentError, ["Type Error: contribution to '#{binding.name}' does not match type of multibind, ", "#{type_error_detail([binding.type.element_type, binding.type], value)}"].join() end end end # @api public class HashMultibindProducer < MultibindProducer # @return [Symbol] One of `:error`, `:merge`, `:append`, `:priority`, `:ignore` # @api public attr_reader :conflict_resolution # @return [Boolean] # @api public attr_reader :uniq # @return [Boolean, Integer] Flatten all if true, or none if false, or to given level (0 = none, -1 = all) # @api public attr_reader :flatten # The hash multibind producer provides options to control conflict resolution. # By default, the hash is produced using `:priority` resolution - the highest entry is selected, the rest are # ignored unless they have the same priority which is an error. # # @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates # @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer # @param scope [Puppet::Parser::Scope] The scope to use for evaluation # @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value # @option options [Symbol, String] :conflict_resolution (:priority) One of `:error`, `:merge`, `:append`, `:priority`, `:ignore` # <ul><li> `ignore` the first found highest priority contribution is used, the rest are ignored</li> # <li>`error` any duplicate key is an error</li> # <li>`append` element type must be compatible with Array, makes elements be arrays and appends all found</li> # <li>`merge` element type must be compatible with hash, merges hashes with retention of highest priority hash content</li> # <li>`priority` the first found highest priority contribution is used, duplicates with same priority raises and error, the rest are # ignored.</li></ul> # @option options [Boolean, Integer] :flatten (false) If appended should be flattened. Also see {#flatten}. # @option options [Boolean] :uniq (false) If appended result should be made unique. # # @api public # def initialize(injector, binding, scope, options) super @conflict_resolution = options[:conflict_resolution].nil? ? :priority : options[:conflict_resolution] @uniq = !!options[:uniq] @flatten = options[:flatten] unless [:error, :merge, :append, :priority, :ignore].include?(@conflict_resolution) raise ArgumentError, "Unknown conflict_resolution for Multibind Hash: '#{@conflict_resolution}." end case @flatten when Integer when true @flatten = -1 when false @flatten = nil when NilClass @flatten = nil else raise ArgumentError, "Option :flatten must be nil, Boolean, or an integer value" unless @flatten.is_a?(Integer) end if uniq || flatten || conflict_resolution.to_s == 'append' etype = binding.type.element_type unless etype.class == Puppet::Pops::Types::PDataType || etype.is_a?(Puppet::Pops::Types::PArrayType) detail = [] detail << ":uniq" if uniq detail << ":flatten" if flatten detail << ":conflict_resolution => :append" if conflict_resolution.to_s == 'append' raise ArgumentError, ["Options #{detail.join(', and ')} cannot be used with a Multibind ", "of type #{injector.type_calculator.string(binding.type)}"].join() end end end protected # @api private def internal_produce(scope) seen = {} included_entries = [] injector.get_contributions(scope, contributions_key).each do |element| key = element[0] entry = element[1] name = entry.binding.name raise ArgumentError, "A Hash Multibind contribution to '#{binding.name}' must have a name." if name.nil? || name.empty? existing = seen[name] if existing case conflict_resolution.to_s when 'priority' # skip if duplicate has lower prio if (comparison = (seen[name] <=> entry)) <= 0 raise ArgumentError, "Internal Error: contributions not given in decreasing precedence order" unless comparison == 0 raise ArgumentError, "Duplicate key (same priority) contributed to Hash Multibinding '#{binding.name}', key: '#{name}'." end next when 'ignore' # skip, ignore conflict if prio is the same next when 'error' raise ArgumentError, "Duplicate key contributed to Hash Multibinding '#{binding.name}', key: '#{name}'." end else seen[name] = entry end included_entries << [key, entry] end result = {} included_entries.each do |element| k = element[ 0 ] entry = element[ 1 ] x = injector.lookup_key(scope, k) name = entry.binding.name assert_type(binding(), injector.type_calculator(), name, x) if result[ name ] merge(result, name, result[ name ], x) else result[ name ] = conflict_resolution().to_s == 'append' ? [x] : x end end result end # @api private def merge(result, name, higher, lower) case conflict_resolution.to_s when 'append' unless higher.is_a?(Array) higher = [higher] end tmp = higher + [lower] tmp.flatten!(flatten) if flatten tmp.uniq! if uniq result[name] = tmp when 'merge' result[name] = lower.merge(higher) end end # @api private def assert_type(binding, tc, key, value) unless tc.instance?(binding.type.key_type, key) raise ArgumentError, ["Type Error: key contribution to #{binding.name}['#{key}'] ", "is incompatible with key type: #{tc.label(binding.type)}, ", type_error_detail(binding.type.key_type, key)].join() end if key.nil? || !key.is_a?(String) || key.empty? raise ArgumentError, "Entry contributing to multibind hash with id '#{binding.id}' must have a name." end unless tc.instance?(binding.type.element_type, value) raise ArgumentError, ["Type Error: value contribution to #{binding.name}['#{key}'] ", "is incompatible, ", type_error_detail(binding.type.element_type, value)].join() end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/scheme_handler/�������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�022221� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/scheme_handler/confdir_scheme.rb��������������������������������0000664�0052762�0001160�00000002300�12650174557�025512� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/pops/binder/scheme_handler/symbolic_scheme' # Similar to {Puppet::Pops::Binder::SchemeHandler::ModuleScheme ModuleScheme}, but relative to the config root. # Does not support wildcard expansion. # # URI # --- # The URI scheme is `confdir:/[<FQN>]['?' | [?optional]` where FQN is the fully qualified name of the bindings to load. # The referecence is made optional by using a URI query of `?` or `?optional`. # # @todo # If the file to load is outside of the file system rooted at $confdir (in a gem, or just on the Ruby path), it can not # be marked as optional as it will always be ignored. # class Puppet::Pops::Binder::SchemeHandler::ConfdirScheme < Puppet::Pops::Binder::SchemeHandler::SymbolicScheme def expand_included(uri, composer) fqn = fqn_from_path(uri)[1] if is_optional?(uri) if Puppet::Pops::Binder::BindingsLoader.loadable?(composer.confdir, fqn) [URI.parse('confdir:/' + fqn)] else [] end else # assume it exists (do not give error if not, since it may be excluded later) [URI.parse('confdir:/' + fqn)] end end def expand_excluded(uri, composer) [URI.parse("confdir:/#{fqn_from_path(uri)[1]}")] end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/scheme_handler/module_scheme.rb���������������������������������0000664�0052762�0001160�00000006106�12650174557�025363� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/pops/binder/scheme_handler/symbolic_scheme' # The module scheme allows loading bindings using the Puppet autoloader. # Optional uris are handled by checking if the symbolic name can be resolved to a loadable file # from modules. # # URI # --- # The uri is on the format: `module:/[fqn][? | ?optional]` where `fqn` is a fully qualified bindings name # starting with the module name or '*' to denote 'any module'. A URI query of `?` or `?optional` makes the # request optional; if no loadable file is found, it is simply skipped. # # # @todo # Does currently only support checking of optionality against files under a module. If the result should be loaded # from any other location it can not be marked as optional as it will be ignored. # class Puppet::Pops::Binder::SchemeHandler::ModuleScheme < Puppet::Pops::Binder::SchemeHandler::SymbolicScheme # Expands URIs with wildcards and checks optionality. # @param uri [URI] the uri to possibly expand # @return [Array<URI>] the URIs to include # @api public # def expand_included(uri, composer) result = [] split_name, fqn = fqn_from_path(uri) # supports wild card in the module name case split_name[0] when '*' # create new URIs, one per module name that has a corresponding .rb file relative to its # '<root>/lib/puppet/bindings/' # composer.name_to_module.each_pair do | mod_name, mod | expanded_name_parts = [mod_name] + split_name[1..-1] expanded_name = expanded_name_parts.join('::') if Puppet::Pops::Binder::BindingsLoader.loadable?(mod.path, expanded_name) result << URI.parse('module:/' + expanded_name) end end when nil raise ArgumentError, "Bad bindings uri, the #{uri} has neither module name or wildcard '*' in its first path position" else joined_name = split_name.join('::') # skip optional uri if it does not exist if is_optional?(uri) mod = composer.name_to_module[split_name[0]] if mod && Puppet::Binder::BindingsLoader.loadable?(mod.path, joined_name) result << URI.parse('module:/' + joined_name) end else # assume it exists (do not give error if not, since it may be excluded later) result << URI.parse('module:/' + joined_name) end end result end # Expands URIs with wildcards # @param uri [URI] the uri to possibly expand # @return [Array<URI>] the URIs to exclude # @api public # def expand_excluded(uri, composer) result = [] split_name, fqn = fqn_from_path(uri) case split_name[ 0 ] when '*' # create new URIs, one per module name composer.name_to_module.each_pair do | name, mod | result << URI.parse('module:/' + ([name] + split_name).join('::')) end when nil raise ArgumentError, "Bad bindings uri, the #{uri} has neither module name or wildcard '*' in its first path position" else # create a clean copy (get rid of optional, fragments etc. and any trailing stuff result << URI.parse('module:/' + split_name.join('::')) end result end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/scheme_handler/symbolic_scheme.rb�������������������������������0000664�0052762�0001160�00000004103�12650174557�025712� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Abstract base class for schemes based on symbolic names of bindings. # This class helps resolve symbolic names by computing a path from a fully qualified name (fqn). # There are also helper methods do determine if the symbolic name contains a wild-card ('*') in the first # portion of the fqn (with the convention that '*' means 'any module'). # # @abstract # @api public # class Puppet::Pops::Binder::SchemeHandler::SymbolicScheme < Puppetx::Puppet::BindingsSchemeHandler # Shared implementation for module: and confdir: since the distinction is only in checks if a symbolic name # exists as a loadable file or not. Once this method is called it is assumed that the name is relative # and that it should exist relative to some loadable ruby location. # # TODO: this needs to be changed once ARM-8 Puppet DSL concrete syntax is also supported. # @api public # def contributed_bindings(uri, scope, composer) fqn = fqn_from_path(uri)[1] bindings = Puppet::Pops::Binder::BindingsLoader.provide(scope, fqn) raise ArgumentError, "Cannot load bindings '#{uri}' - no bindings found." unless bindings # Must clone as the rest mutates the model cloned_bindings = Marshal.load(Marshal.dump(bindings)) Puppet::Pops::Binder::BindingsFactory.contributed_bindings(fqn, cloned_bindings) end # @api private def fqn_from_path(uri) split_path = uri.path.split('/') if split_path.size > 1 && split_path[-1].empty? split_path.delete_at(-1) end fqn = split_path[ 1 ] raise ArgumentError, "Module scheme binding reference has no name." unless fqn split_name = fqn.split('::') # drop leading '::' split_name.shift if split_name[0] && split_name[0].empty? [split_name, split_name.join('::')] end # True if super thinks it is optional or if it contains a wildcard. # @return [Boolean] true if the uri represents an optional set of bindings. # @api public def is_optional?(uri) super(uri) || has_wildcard?(uri) end # @api private def has_wildcard?(uri) (path = uri.path) && path.split('/')[1].start_with?('*::') end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/binder/system_bindings.rb����������������������������������������������0000664�0052762�0001160�00000003645�12650174557�023017� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Pops::Binder::SystemBindings # Constant with name used for bindings used during initialization of injector ENVIRONMENT_BOOT_BINDINGS_NAME = 'puppet::env::injector::boot' Factory = Puppet::Pops::Binder::BindingsFactory @extension_bindings = Factory.named_bindings("puppet::extensions") @default_bindings = Factory.named_bindings("puppet::default") # Bindings in effect when real injector is created @injector_boot_bindings = Factory.named_bindings("puppet::injector_boot") def self.extensions() @extension_bindings end def self.default_bindings() @default_bindings end def self.injector_boot_bindings() @injector_boot_bindings end def self.final_contribution Factory.contributed_bindings("puppet-final", [deep_clone(@extension_bindings.model)]) end def self.default_contribution Factory.contributed_bindings("puppet-default", [deep_clone(@default_bindings.model)]) end def self.injector_boot_contribution(env_boot_bindings) # Compose the injector_boot_bindings contributed from the puppet runtime book (i.e. defaults for # extensions that should be active in the boot injector - see Puppetx initialization. # bindings = [deep_clone(@injector_boot_bindings.model), deep_clone(@injector_default_bindings)] # Add the bindings that come from the bindings_composer as it may define custom extensions added in the bindings # configuration. (i.e. bindings required to be able to lookup using bindings schemes and backends when # configuring the real injector). # bindings << env_boot_bindings unless env_boot_bindings.nil? # return the composition Factory.contributed_bindings("puppet-injector-boot", bindings) end def self.factory() Puppet::Pops::Binder::BindingsFactory end def self.type_factory() Puppet::Pops::Types::TypeFactory end private def self.deep_clone(o) Marshal.load(Marshal.dump(o)) end end �������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/containment.rb���������������������������������������������������������0000664�0052762�0001160�00000005255�12650174557�020671� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# FIXME: This module should be updated when a newer version of RGen (>0.6.2) adds required meta model "e-method" supports. # require 'rgen/ecore/ecore' module Puppet::Pops::Containment # Returns Enumerable, thus allowing # some_element.eAllContents each {|contained| } # This is a depth first enumeration where parent appears before children. # @note the top-most object itself is not included in the enumeration, only what it contains. def eAllContents EAllContentsEnumerator.new(self) end def eAllContainers EAllContainersEnumerator.new(self) end class EAllContainersEnumerator include Enumerable def initialize o @element = o end def each &block if block_given? eAllContainers(@element, &block) else self end end def eAllContainers(element, &block) x = element.eContainer while !x.nil? do yield x x = x.eContainer end end end class EAllContentsEnumerator include Enumerable def initialize o @element = o @@cache ||= {} end def each &block if block_given? eAllContents(@element, &block) @element else self end end def eAllContents(element, &block) # This method is performance critical and code has been manually in-lined. # Resist the urge to make this pretty. # The slow way is element.eAllContainments.each {|c| element.getGenericsAsArray(c.name) } # (@@cache[element.class] || all_containment_getters(element)).each do |r| children = element.send(r) if children.is_a?(Array) children.each do |c| yield c eAllContents(c, &block) end elsif !children.nil? yield children eAllContents(children, &block) end end end private def all_containment_getters(element) elem_class = element.class containments = [] collect_getters(elem_class.ecore, containments) @@cache[elem_class] = containments end def collect_getters(eclass, containments) eclass.eStructuralFeatures.select {|r| r.is_a?(RGen::ECore::EReference) && r.containment}.each do |r| n = r.name containments << :"get#{n[0..0].upcase + ( n[1..-1] || "" )}" end eclass.eSuperTypes.each do |t| if cached = @@cache[ t.instanceClass ] containments.concat(cached) else super_containments = [] collect_getters(t, super_containments) @@cache[ t.instanceClass ] = super_containments containments.concat(super_containments) end end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/evaluator/�������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020017� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/evaluator/access_operator.rb�������������������������������������������0000664�0052762�0001160�00000056713�12650174557�023535� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# AccessOperator handles operator [] # This operator is part of evaluation. # class Puppet::Pops::Evaluator::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 Puppet::Pops::Evaluator::Runtime3Support Issues = Puppet::Pops::Issues TYPEFACTORY = Puppet::Pops::Types::TypeFactory EMPTY_STRING = ''.freeze attr_reader :semantic # Initialize with AccessExpression to enable reporting issues # @param access_expression [Puppet::Pops::Model::AccessExpression] the semantic object being evaluated # @return [void] # def initialize(access_expression) @@access_visitor ||= Puppet::Pops::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) fail(Issues::OPERATOR_NOT_APPLICABLE, @semantic.left_expr, :operator=>'[]', :left_value => o) end def access_String(o, scope, keys) keys.flatten! result = case keys.size when 0 fail(Puppet::Pops::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 = coerce_numeric(keys[0], @semantic.keys[0], scope) bad_access_key_type(o, 0, k1, Integer) 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 = 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) } 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(Puppet::Pops::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(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, blamed, :base_type => o, :min=>1, :actual => keys.size) end assert_keys(keys, o, 1, 1, String, Regexp) Puppet::Pops::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(Puppet::Pops::Issues::BAD_ARRAY_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) when 1 k = coerce_numeric(keys[0], @semantic.keys[0], scope) unless k.is_a?(Integer) bad_access_key_type(o, 0, k, Integer) end o[k] 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(Puppet::Pops::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(Puppet::Pops::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 # Ruby does not have an infinity constant. TODO: Consider having one constant in Puppet. Now it is in several places. INFINITY = 1.0 / 0.0 def access_PEnumType(o, scope, keys) keys.flatten! assert_keys(keys, o, 1, INFINITY, String) Puppet::Pops::Types::TypeFactory.enum(*keys) end def access_PVariantType(o, scope, keys) keys.flatten! assert_keys(keys, o, 1, INFINITY, Puppet::Pops::Types::PAnyType) Puppet::Pops::Types::TypeFactory.variant(*keys) end def access_PTupleType(o, scope, keys) keys.flatten! if TYPEFACTORY.is_range_parameter?(keys[-2]) && TYPEFACTORY.is_range_parameter?(keys[-1]) size_type = TYPEFACTORY.range(keys[-2], keys[-1]) keys = keys[0, keys.size - 2] elsif TYPEFACTORY.is_range_parameter?(keys[-1]) size_type = TYPEFACTORY.range(keys[-1], :default) keys = keys[0, keys.size - 1] end assert_keys(keys, o, 1, INFINITY, Puppet::Pops::Types::PAnyType) t = Puppet::Pops::Types::TypeFactory.tuple(*keys) # set size type, or nil for default (exactly 1) t.size_type = size_type t end def access_PCallableType(o, scope, keys) TYPEFACTORY.callable(*keys) end def access_PStructType(o, scope, keys) assert_keys(keys, o, 1, 1, Hash) 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(Puppet::Pops::Issues::BAD_STRING_SLICE_ARITY, @semantic, {:actual => keys.size}) end string_t = Puppet::Pops::Types::TypeFactory.string() string_t.size_type = size_t string_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 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 || INFINITY) fail(Puppet::Pops::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(Puppet::Pops::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_key_type_name(actual) case actual when nil 'Undef' when :default 'Default' else Puppet::Pops::Types::TypeCalculator.generalize!(Puppet::Pops::Types::TypeCalculator.infer(actual)).to_s end end def bad_type_specialization_key_type(type, key_index, actual, *expected_classes) label_provider = Puppet::Pops::Model::ModelLabelProvider.new() expected = expected_classes.map {|c| label_provider.label(c) }.join(' or ') fail(Puppet::Pops::Issues::BAD_TYPE_SPECIALIZATION, @semantic.keys[key_index], { :type => type, :message => "Cannot use #{bad_key_type_name(actual)} where #{expected} is expected" }) end def access_PPatternType(o, scope, keys) keys.flatten! assert_keys(keys, o, 1, INFINITY, String, Regexp, Puppet::Pops::Types::PPatternType, Puppet::Pops::Types::PRegexpType) Puppet::Pops::Types::TypeFactory.pattern(*keys) end def access_POptionalType(o, scope, keys) keys.flatten! if keys.size == 1 type = keys[0] unless type.is_a?(Puppet::Pops::Types::PAnyType) || type.is_a?(String) fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Optional-Type', :actual => type.class}) end TYPEFACTORY.optional(type) else fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Optional-Type', :min => 1, :actual => keys.size}) end end def access_PNotUndefType(o, scope, keys) keys.flatten! case keys.size when 0 TYPEFACTORY.not_undef when 1 type = keys[0] case type when String type = TYPEFACTORY.string(type) when Puppet::Pops::Types::PAnyType type = nil if type.class == Puppet::Pops::Types::PAnyType else fail(Puppet::Pops::Issues::BAD_NOT_UNDEF_SLICE_TYPE, @semantic.keys[0], {:base_type => 'NotUndef-Type', :actual => type.class}) end TYPEFACTORY.not_undef(type) else fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'NotUndef-Type', :min => 0, :max => 1, :actual => keys.size}) end end def access_PType(o, scope, keys) keys.flatten! if keys.size == 1 unless keys[0].is_a?(Puppet::Pops::Types::PAnyType) fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Type-Type', :actual => keys[0].class}) end result = Puppet::Pops::Types::PType.new() result.type = keys[0] result else fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Type-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) Puppet::Pops::Types::TypeFactory.runtime(*keys) end def access_PIntegerType(o, scope, keys) keys.flatten! unless keys.size.between?(1, 2) fail(Puppet::Pops::Issues::BAD_INTEGER_SLICE_ARITY, @semantic, {:actual => keys.size}) end keys.each_with_index do |x, index| fail(Puppet::Pops::Issues::BAD_INTEGER_SLICE_TYPE, @semantic.keys[index], {:actual => x.class}) unless (x.is_a?(Integer) || x == :default) end ranged_integer = Puppet::Pops::Types::PIntegerType.new() from, to = keys # NOTE! Do not merge the following line to 4.x. It has the same check in the initialize method raise ArgumentError, "'from' must be less or equal to 'to'. Got (#{from}, #{to}" if from.is_a?(Numeric) && to.is_a?(Numeric) && from > to ranged_integer.from = from == :default ? nil : from ranged_integer.to = to == :default ? nil : to ranged_integer end def access_PFloatType(o, scope, keys) keys.flatten! unless keys.size.between?(1, 2) fail(Puppet::Pops::Issues::BAD_FLOAT_SLICE_ARITY, @semantic, {:actual => keys.size}) end keys.each_with_index do |x, index| fail(Puppet::Pops::Issues::BAD_FLOAT_SLICE_TYPE, @semantic.keys[index], {:actual => x.class}) unless (x.is_a?(Float) || x.is_a?(Integer) || x == :default) end ranged_float = Puppet::Pops::Types::PFloatType.new() from, to = keys # NOTE! Do not merge the following line to 4.x. It has the same check in the initialize method raise ArgumentError, "'from' must be less or equal to 'to'. Got (#{from}, #{to}" if from.is_a?(Numeric) && to.is_a?(Numeric) && from > to ranged_float.from = from == :default || from.nil? ? nil : Float(from) ranged_float.to = to == :default || to.nil? ? nil : Float(to) ranged_float 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! keys[0,2].each_with_index do |k, index| unless k.is_a?(Puppet::Pops::Types::PAnyType) fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[index], {:base_type => 'Hash-Type', :actual => k.class}) end end case keys.size when 2 result = Puppet::Pops::Types::PHashType.new() result.key_type = keys[0] result.element_type = keys[1] result when 3 result = Puppet::Pops::Types::PHashType.new() result.key_type = keys[0] result.element_type = keys[1] size_t = collection_size_t(1, keys[2]) result when 4 result = Puppet::Pops::Types::PHashType.new() result.key_type = keys[0] result.element_type = keys[1] size_t = collection_size_t(1, keys[2], keys[3]) result else fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic, { :base_type => 'Hash-Type', :min => 2, :max => 4, :actual => keys.size }) end result.size_type = size_t if size_t result 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(1, keys[0]) when 2 size_t = collection_size_t(1, keys[0], keys[1]) else fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Collection-Type', :min => 1, :max => 2, :actual => keys.size}) end result = Puppet::Pops::Types::PCollectionType.new() result.size_type = size_t result 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 size_t = nil when 2 size_t = collection_size_t(1, keys[1]) when 3 size_t = collection_size_t(1, keys[1], keys[2]) else fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Array-Type', :min => 1, :max => 3, :actual => keys.size}) end unless keys[0].is_a?(Puppet::Pops::Types::PAnyType) fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Array-Type', :actual => keys[0].class}) end result = Puppet::Pops::Types::PArrayType.new() result.element_type = keys[0] result.size_type = size_t result 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?(Puppet::Pops::Types::PIntegerType) keys[0].copy else keys.each_with_index do |x, index| fail(Puppet::Pops::Issues::BAD_COLLECTION_SLICE_TYPE, @semantic.keys[start_index + index], {:actual => x.class}) unless (x.is_a?(Integer) || x == :default) end ranged_integer = Puppet::Pops::Types::PIntegerType.new() from, to = keys # NOTE! Do not merge the following line to 4.x. It has the same check in the initialize method raise ArgumentError, "'from' must be less or equal to 'to'. Got (#{from}, #{to}" if from.is_a?(Numeric) && to.is_a?(Numeric) && from > to ranged_integer.from = from == :default ? nil : from ranged_integer.to = to == :default ? nil : to ranged_integer 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 = Puppet::Pops::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 # 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(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, blamed, :base_type => Puppet::Pops::Types::TypeCalculator.new().string(o), :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 || keys.shift type_name = case type_name when Puppet::Pops::Types::PResourceType type_name.type_name when String type_name.downcase 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(Puppet::Pops::Issues::ILLEGAL_RESOURCE_SPECIALIZATION, blame, {:actual => bad_key_type_name(type_name)}) end # type name must conform if type_name !~ Puppet::Pops::Patterns::CLASSREF fail(Puppet::Pops::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(Puppet::Pops::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(Puppet::Pops::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(Puppet::Pops::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(Puppet::Pops::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 rtype = Puppet::Pops::Types::PResourceType.new() rtype.type_name = type_name rtype.title = (t == :no_title ? nil : t) rtype 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 def access_PHostClassType(o, scope, keys) blamed = keys.size == 0 ? @semantic : @semantic.keys[0] keys_orig_size = keys.size if keys_orig_size == 0 fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, blamed, :base_type => Puppet::Pops::Types::TypeCalculator.new().string(o), :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(Puppet::Pops::Issues::EMPTY_RESOURCE_SPECIALIZATION, blamed) return result_type_array ? [] : nil end if o.class_name.nil? # The type argument may be a Resource Type - the Puppet Language allows a reference such as # Class[Foo], and this is interpreted as Class[Resource[Foo]] - which is ok as long as the resource # does not have a title. This should probably be deprecated. # result = keys.each_with_index.map do |c, i| name = if c.is_a?(Puppet::Pops::Types::PResourceType) && !c.type_name.nil? && c.title.nil? # type_name is already downcase. Don't waste time trying to downcase again c.type_name elsif c.is_a?(String) c.downcase else fail(Puppet::Pops::Issues::ILLEGAL_HOSTCLASS_NAME, @semantic.keys[i], {:name => c}) end if name =~ Puppet::Pops::Patterns::NAME ctype = Puppet::Pops::Types::PHostClassType.new() # Remove leading '::' since all references are global, and 3x runtime does the wrong thing ctype.class_name = name.sub(/^::/, EMPTY_STRING) ctype else fail(Issues::ILLEGAL_NAME, @semantic.keys[i], {:name=>c}) end 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(Puppet::Pops::Issues::UNKNOWN_RESOURCE_PARAMETER, @semantic, {:type_name => 'Class', :title => o.class_name, :param_name=>k}) end end else fail(Puppet::Pops::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 �����������������������������������������������������puppet-3.8.5/lib/puppet/pops/evaluator/callable_mismatch_describer.rb�������������������������������0000664�0052762�0001160�00000013512�12650174557�026015� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# @api private module Puppet::Pops::Evaluator::CallableMismatchDescriber # Produces a string with the difference between the given arguments and support signature(s). # # @param name [String] The name of the callable to describe # @param args_type [Puppet::Pops::Types::Tuple] The tuple of argument types. # @param supported_signatures [Array<Puppet::Pops::Types::Callable>] The available signatures that were available for calling. # # @api private def self.diff_string(name, args_type, supported_signatures) result = [ ] if supported_signatures.size == 1 signature = supported_signatures[0] params_type = signature.type.param_types block_type = signature.type.block_type params_names = signature.parameter_names result << "expected:\n #{name}(#{signature_string(signature)}) - #{arg_count_string(signature.type)}" else result << "expected one of:\n" result << supported_signatures.map do |signature| params_type = signature.type.param_types " #{name}(#{signature_string(signature)}) - #{arg_count_string(signature.type)}" end.join("\n") end result << "\nactual:\n #{name}(#{arg_types_string(args_type)}) - #{arg_count_string(args_type)}" result.join('') end private # Produces a string for the signature(s) # # @api private def self.signature_string(signature) param_types = signature.type.param_types block_type = signature.type.block_type param_names = signature.parameter_names from, to = param_types.size_range if from == 0 && to == 0 # No parameters function return '' end required_count = from # there may be more names than there are types, and count needs to be subtracted from the count # to make it correct for the last named element adjust = max(0, param_names.size() -1) last_range = [max(0, (from - adjust)), (to - adjust)] types = case param_types when Puppet::Pops::Types::PTupleType param_types.types when Puppet::Pops::Types::PArrayType [ param_types.element_type ] end tc = Puppet::Pops::Types::TypeCalculator # join type with names (types are always present, names are optional) # separate entries with comma # result = if param_names.empty? types.each_with_index.map {|t, index| tc.string(t) + opt_value_indicator(index, required_count, 0) } else limit = param_names.size result = param_names.each_with_index.map do |name, index| [tc.string(types[index] || types[-1]), name].join(' ') + opt_value_indicator(index, required_count, limit) end end.join(', ') # Add {from, to} for the last type # This works for both Array and Tuple since it describes the allowed count of the "last" type element # for both. It does not show anything when the range is {1,1}. # result += range_string(last_range) # If there is a block, include it with its own optional count {0,1} case signature.type.block_type when Puppet::Pops::Types::POptionalType result << ', ' unless result == '' result << "#{tc.string(signature.type.block_type.optional_type)} #{signature.block_name} {0,1}" when Puppet::Pops::Types::PCallableType result << ', ' unless result == '' result << "#{tc.string(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 self.max(a, b) a >= b ? a : b end # @api private def self.opt_value_indicator(index, required_count, limit) count = index + 1 (count > required_count && count < limit) ? '?' : '' end # @api private def self.arg_count_string(args_type) if args_type.is_a?(Puppet::Pops::Types::PCallableType) size_range = args_type.param_types.size_range # regular parameters adjust_range= case args_type.block_type when Puppet::Pops::Types::POptionalType size_range[1] += 1 when Puppet::Pops::Types::PCallableType size_range[0] += 1 size_range[1] += 1 when NilClass # nothing else raise ArgumentError, "Internal Error, only nil, Callable, and Optional[Callable] supported by Callable block type" end else size_range = args_type.size_range end "arg count #{range_string(size_range, false)}" end # @api private def self.arg_types_string(args_type) types = case args_type when Puppet::Pops::Types::PTupleType last_range = args_type.repeat_last_range args_type.types when Puppet::Pops::Types::PArrayType last_range = args_type.size_range [ args_type.element_type ] end # stringify generalized versions or it will display Integer[10,10] for "10", String['the content'] etc. # note that type must be copied since generalize is a mutating operation tc = Puppet::Pops::Types::TypeCalculator result = types.map { |t| tc.string(tc.generalize!(t.copy)) }.join(', ') # Add {from, to} for the last type # This works for both Array and Tuple since it describes the allowed count of the "last" type element # for both. It does not show anything when the range is {1,1}. # result += range_string(last_range) result end # Formats a range into a string of the form: `{from, to}` # # The following cases are optimized: # # * from and to are equal => `{from}` # * from and to are both and 1 and squelch_one == true => `''` # * from is 0 and to is 1 => `'?'` # * to is INFINITY => `{from, }` # # @api private def self.range_string(size_range, squelch_one = true) from, to = size_range if from == to (squelch_one && from == 1) ? '' : "{#{from}}" elsif to == Puppet::Pops::Types::INFINITY "{#{from},}" elsif from == 0 && to == 1 '?' else "{#{from},#{to}}" end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/evaluator/callable_signature.rb����������������������������������������0000664�0052762�0001160�00000006202�12650174557�024165� 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 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 == Puppet::Pops::Types::INFINITY end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/evaluator/closure.rb���������������������������������������������������0000664�0052762�0001160�00000016711�12650174557�022027� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� # A Closure represents logic bound to a particular scope. # As long as the runtime (basically the scope implementation) has the behaviour of Puppet 3x it is not # safe to use this closure when the scope given to it when initialized goes "out of scope". # # Note that the implementation is backwards compatible in that the call method accepts a scope, but this # scope is not used. # # 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 Puppet::Pops::Evaluator::Closure < Puppet::Pops::Evaluator::CallableSignature attr_reader :evaluator attr_reader :model attr_reader :enclosing_scope def initialize(evaluator, model, scope) @evaluator = evaluator @model = model @enclosing_scope = scope end # marker method checked with respond_to :puppet_lambda # @api private # @deprecated Use the type system to query if an object is of Callable type, then use its signatures method for info def puppet_lambda() true end # compatible with 3x AST::Lambda # @api public def call(*args) variable_bindings = combine_values_with_parameters(args) tc = Puppet::Pops::Types::TypeCalculator final_args = tc.infer_set(parameters.inject([]) do |final_args, param| if param.captures_rest final_args.concat(variable_bindings[param.name]) else final_args << variable_bindings[param.name] end end) if tc.callable?(type, final_args) @evaluator.evaluate_block_with_bindings(@enclosing_scope, variable_bindings, @model.body) else raise ArgumentError, "lambda called with mis-matched arguments\n#{Puppet::Pops::Evaluator::CallableMismatchDescriber.diff_string('lambda', final_args, [self])}" end end # Call closure with argument assignment by name def call_by_name(scope, args_hash, enforce_parameters) if enforce_parameters if args_hash.size > parameters.size raise ArgumentError, "Too many arguments: #{args_hash.size} for #{parameters.size}" end # associate values with parameters scope_hash = {} parameters.each do |p| name = p.name if (arg_value = args_hash[name]).nil? # only set result of default expr if it is defined (it is otherwise not possible to differentiate # between explicit undef and no default expression unless p.value.nil? scope_hash[name] = @evaluator.evaluate(p.value, @enclosing_scope) end else scope_hash[name] = arg_value end end missing = parameters.select { |p| !scope_hash.include?(p.name) } if missing.any? raise ArgumentError, "Too few arguments; no value given for required parameters #{missing.collect(&:name).join(" ,")}" end tc = Puppet::Pops::Types::TypeCalculator final_args = tc.infer_set(parameter_names.collect { |param| scope_hash[param] }) if !tc.callable?(type, final_args) raise ArgumentError, "lambda called with mis-matched arguments\n#{Puppet::Pops::Evaluator::CallableMismatchDescriber.diff_string('lambda', final_args, [self])}" end else scope_hash = args_hash end @evaluator.evaluate_block_with_bindings(@enclosing_scope, scope_hash, @model.body) end 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 # @api public def type @callable ||= create_callable_type 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 private def combine_values_with_parameters(args) variable_bindings = {} 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 = @evaluator.evaluate(default_expression, @enclosing_scope) 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(Puppet::Pops::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 = @evaluator.evaluate(default_expression, scope) # and ensure it is an array value = [value] unless value.is_a?(Array) end else value = given_argument end end variable_bindings[parameter.name] = value end variable_bindings end def create_callable_type() types = [] range = [0, 0] in_optional_parameters = false parameters.each do |param| type = if param.type_expr @evaluator.evaluate(param.type_expr, @enclosing_scope) else Puppet::Pops::Types::TypeFactory.any() end if param.captures_rest && type.is_a?(Puppet::Pops::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?(Puppet::Pops::Types::PArrayType) param_range = ANY_NUMBER_RANGE elsif param.value param_range = OPTIONAL_SINGLE_RANGE else param_range = REQUIRED_SINGLE_RANGE end types << type if param_range[0] == 0 in_optional_parameters = true elsif param_range[0] != 0 && in_optional_parameters @evaluator.fail(Puppet::Pops::Issues::REQUIRED_PARAMETER_AFTER_OPTIONAL, param, { :param_name => param.name }) end range[0] += param_range[0] range[1] += param_range[1] end if range[1] == Puppet::Pops::Types::INFINITY range[1] = :default end Puppet::Pops::Types::TypeFactory.callable(*(types + range)) end # Produces information about parameters compatible with a 4x Function (which can have multiple signatures) def signatures [ self ] end ANY_NUMBER_RANGE = [0, Puppet::Pops::Types::INFINITY] OPTIONAL_SINGLE_RANGE = [0, 1] REQUIRED_SINGLE_RANGE = [1, 1] end �������������������������������������������������������puppet-3.8.5/lib/puppet/pops/evaluator/collector_transformer.rb�������������������������������������0000664�0052762�0001160�00000013042�12650174557�024755� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Pops::Evaluator::CollectorTransformer def initialize @@query_visitor ||= Puppet::Pops::Visitor.new(nil, "query", 1, 1) @@match_visitor ||= Puppet::Pops::Visitor.new(nil, "match", 1, 1) @@evaluator ||= Puppet::Pops::Evaluator::EvaluatorImpl.new @@compare_operator ||= Puppet::Pops::Evaluator::CompareOperator.new() end def transform(o, scope) raise ArgumentError, "Expected CollectExpression" unless o.is_a? Puppet::Pops::Model::CollectExpression raise "LHS is not a type" unless o.type_expr.is_a? Puppet::Pops::Model::QualifiedReference type = o.type_expr.value().downcase() if type == 'class' fail "Classes cannot be collected" end resource_type = scope.find_resource_type(type) fail "Resource type #{type} doesn't exist" unless resource_type adapter = Puppet::Pops::Adapters::SourcePosAdapter.adapt(o) line_num = adapter.line position = adapter.pos file_path = adapter.locator.file if !o.operations.empty? overrides = { :parameters => o.operations.map{ |x| @@evaluator.evaluate(x, scope)}.flatten, :file => file_path, :line => [line_num, position], :source => scope.source, :scope => scope } end code = query_unless_nop(o.query, scope) case o.query when Puppet::Pops::Model::VirtualQuery newcoll = Puppet::Pops::Evaluator::Collectors::CatalogCollector.new(scope, resource_type.name, code, overrides) when Puppet::Pops::Model::ExportedQuery match = match_unless_nop(o.query, scope) newcoll = Puppet::Pops::Evaluator::Collectors::ExportedCollector.new(scope, resource_type.name, 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?(Puppet::Pops::Model::Nop) query(query.expr, scope) end end def match_unless_nop(query, scope) unless query.expr.nil? || query.expr.is_a?(Puppet::Pops::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_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_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 #{o.class}" 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_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 #{o.class}" end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/evaluator/collectors/��������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�022170� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/evaluator/collectors/abstract_collector.rb�����������������������������0000664�0052762�0001160�00000004643�12650174557�026376� 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) newres = Puppet::Parser::Resource.new(res.type, res.title, @overrides) scope.compiler.add_override(newres) end end end objects.reject! { |o| @collected.include?(o.ref) } return false if objects.empty? objects.inject(@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-3.8.5/lib/puppet/pops/evaluator/collectors/catalog_collector.rb������������������������������0000664�0052762�0001160�00000001400�12650174557�026171� 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 [Symbol] 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 end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/evaluator/collectors/exported_collector.rb�����������������������������0000664�0052762�0001160�00000004177�12650174557�026427� 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 [Symbol] 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 Puppet.warning "Not collecting exported resources without storeconfigs" 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.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 #{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 end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/evaluator/collectors/fixed_set_collector.rb����������������������������0000664�0052762�0001160�00000002043�12650174557�026535� 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-3.8.5/lib/puppet/pops/evaluator/compare_operator.rb������������������������������������������0000664�0052762�0001160�00000012543�12650174557�023713� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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 Puppet::Pops::Evaluator::CompareOperator include Puppet::Pops::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 Puppet::Pops::Evaluator::Runtime3Support def initialize @@equals_visitor ||= Puppet::Pops::Visitor.new(self, "equals", 1, 1) @@compare_visitor ||= Puppet::Pops::Visitor.new(self, "cmp", 1, 1) @@match_visitor ||= Puppet::Pops::Visitor.new(self, "match", 2, 2) @@include_visitor ||= Puppet::Pops::Visitor.new(self, "include", 2, 2) @type_calculator = Puppet::Pops::Types::TypeCalculator.new() 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) @@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_Object(a, b) raise ArgumentError.new("Only Strings and Numbers 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 # subsstring 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_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 Puppet::Pops::Types::PAnyType a.each {|element| return true if @type_calculator.instance?(b, 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 # 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) # creates or clears ephemeral !!matched # convert to boolean 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). # @type_calculator.instance?(any_type, 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, scope) end end �������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/evaluator/epp_evaluator.rb���������������������������������������������0000664�0052762�0001160�00000006537�12650174557�023226� 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) raise ArgumentError, "inline_epp(): the first argument must be a String with the epp source text, got a #{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 raise ArgumentError, "inline_epp(): Invalid EPP: #{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) raise ArgumentError, "epp(): the first argument must be a String with the filename, got a #{file.class}" end file = file + ".epp" unless file =~ /\.epp$/ scope.debug "Retrieving epp template #{file}" template_file = Puppet::Parser::Files.find_template(file, env_name) unless template_file raise Puppet::ParseError, "Could not find template '#{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 raise ArgumentError, "epp(): Invalid EPP: #{e.message}" end # Evaluate (and check template_args) evaluate(parser, 'epp', scope, true, result, template_args) end private 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) raise ArgumentError, "#{func_name}(): the parser did not produce a LambdaExpression, got '#{body.class}'" end unless body.body.is_a?(Puppet::Pops::Model::EppExpression) raise ArgumentError, "#{func_name}(): the parser did not produce an EppExpression, got '#{body.body.class}'" end unless parse_result.definitions.empty? raise ArgumentError, "#{func_name}(): The EPP template contains illegal expressions (definitions)" end parameters_specified = body.body.parameters_specified if parameters_specified || template_args_set # no epp params or user provided arguments in a hash, epp() logic # only sees global + what was given closure_scope = scope.find_global_scope enforce_parameters = parameters_specified else # no epp params and no arguments were given => inline_epp() logic # sees all local variables, epp() all global closure_scope = use_global_scope_only ? scope.find_global_scope : scope enforce_parameters = true end evaluated_result = parser.closure(body, closure_scope).call_by_name(scope, template_args, enforce_parameters) evaluated_result end def self.handle_template_args(func_name, template_args) if template_args.nil? [{}, false] else unless template_args.is_a?(Hash) raise ArgumentError, "#{func_name}(): the template_args must be a Hash, got a #{template_args.class}" end [template_args, true] end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/evaluator/evaluator_impl.rb��������������������������������������������0000664�0052762�0001160�00000123310�12650174557�023370� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'rgen/ecore/ecore' 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' # This implementation of {Puppet::Pops::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 {Puppet::Pops::Visitable} and {Puppet::Pops::Visitor} for more information about # polymorphic calling. # class Puppet::Pops::Evaluator::EvaluatorImpl include Puppet::Pops::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 Puppet::Pops::Evaluator::Runtime3Support include Puppet::Pops::Evaluator::ExternalSyntaxSupport # This constant is not defined as Float::INFINITY in Ruby 1.8.7 (but is available in later version # Refactor when support is dropped for Ruby 1.8.7. # INFINITY = 1.0 / 0.0 EMPTY_STRING = ''.freeze COMMA_SEPARATOR = ', '.freeze # Reference to Issues name space makes it easier to refer to issues # (Issues are shared with the validator). # Issues = Puppet::Pops::Issues def initialize @@eval_visitor ||= Puppet::Pops::Visitor.new(self, "eval", 1, 1) @@lvalue_visitor ||= Puppet::Pops::Visitor.new(self, "lvalue", 1, 1) @@assign_visitor ||= Puppet::Pops::Visitor.new(self, "assign", 3, 3) @@string_visitor ||= Puppet::Pops::Visitor.new(self, "string", 1, 1) @@type_calculator ||= Puppet::Pops::Types::TypeCalculator.new() @@type_parser ||= Puppet::Pops::Types::TypeParser.new() @@compare_operator ||= Puppet::Pops::Evaluator::CompareOperator.new() @@relationship_operator ||= Puppet::Pops::Evaluator::RelationshipOperator.new() # Use null migration checker unless given in context @migration_checker = (Puppet.lookup(:migration_checker) { Puppet::Pops::Migration::MigrationChecker.new() }) end # @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 Puppet::Pops::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 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 [Puppet::Pops::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 [Puppet::Pops::Model::BlockExpression] the sequence of expressions to evaluate in the new scope # # @api private # def evaluate_block_with_bindings(scope, variable_bindings, block_expr) with_guarded_scope(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 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 # 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 [Puppet::Pops::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 eval_Factory(o, scope) evaluate(o.current, 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(Puppet::Pops::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 PType # def eval_QualifiedReference(o, scope) @@type_parser.interpret(o) 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 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 = [:'+', :'-', :'*', :'/', :'%', :'<<', :'>>'] COLLECTION_OPERATORS = [:'+', :'-', :'<<'] # 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.operator, o.left_expr, o.right_expr, 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, operator, left_o, right_o, scope) unless ARITHMETIC_OPERATORS.include?(operator) fail(Issues::UNSUPPORTED_OPERATOR, left_o.eContainer, {:operator => o.operator}) end if (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, right_o, 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}) end result = left.send(operator, right) rescue NoMethodError => e fail(Issues::OPERATOR_NOT_APPLICABLE, left_o, {:operator => operator, :left_value => left}) rescue ZeroDivisionError => e fail(Issues::DIV_BY_ZERO, right_o) end if result == INFINITY || result == -INFINITY fail(Issues::RESULT_IS_INFINITY, left_o, {:operator => operator}) 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.nil? ? [] : o.keys.collect {|key| evaluate(key, scope) } Puppet::Pops::Evaluator::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) @migration_checker.report_uc_bareword_type(left, o.left_expr) @migration_checker.report_uc_bareword_type(right, o.right_expr) begin # Left is a type if left.is_a?(Puppet::Pops::Types::PAnyType) case o.operator when :'==' @migration_checker.report_equality_type_mismatch(left, right, o) @@type_calculator.equals(left,right) when :'!=' @migration_checker.report_equality_type_mismatch(left, right, o) !@@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 :'==' @migration_checker.report_equality_type_mismatch(left, right, o) @@compare_operator.equals(left,right) when :'!=' @migration_checker.report_equality_type_mismatch(left, right, o) ! @@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) @migration_checker.report_uc_bareword_type(left, o.left_expr) # matches RHS types as instance of for all types except a parameterized Regexp[R] if pattern.is_a?(Puppet::Pops::Types::PAnyType) # evaluate as instance? of type check matched = @@type_calculator.instance?(pattern, left) # convert match result to Boolean true, or false 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) @migration_checker.report_uc_bareword_type(left, o.left_expr) @migration_checker.report_in_expression(o) @@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 r = nil o.statements.each {|s| r = evaluate(s, scope)} r 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. # with_guarded_scope(scope) do test = evaluate(o.test, scope) @migration_checker.report_uc_bareword_type(test, o.test) 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 Puppet::Pops::Model::LiteralDefault the_default = co.then_expr next false when Puppet::Pops::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 evaulate 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 Puppet::Pops::Evaluator::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) evaluate(o.body, scope) 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?(Puppet::Pops::Model::QualifiedName) tmp_name.value # already validated as a name else type_name_acceptable = case o.type_name when Puppet::Pops::Model::QualifiedReference true when Puppet::Pops::Model::AccessExpression o.type_name.left_expr.is_a?(Puppet::Pops::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(Puppet::Pops::Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual => actual}) end # must be a CatalogEntry subtype case evaluated_name when Puppet::Pops::Types::PHostClassType unless evaluated_name.class_name.nil? fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual=> evaluated_name.to_s}) end 'class' when Puppet::Pops::Types::PResourceType unless evaluated_name.title().nil? fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual=> evaluated_name.to_s}) end evaluated_name.type_name # assume validated else actual = type_calculator.generalize!(type_calculator.infer(evaluated_name)).to_s fail(Puppet::Pops::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(Puppet::Pops::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(Puppet::Pops::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(Puppet::Pops::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(Puppet::Pops::Issues::ILLEGAL_TITLE_TYPE_AT, body.title, {:index => index, :actual => actual}) elsif title == EMPTY_STRING fail(Puppet::Pops::Issues::EMPTY_STRING_TITLE_AT, body.title, {:index => index}) elsif titles_to_body[title] fail(Puppet::Pops::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(Puppet::Pops::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(Puppet::Pops::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?(Puppet::Pops::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) # The functor expression is not evaluated, it is not possible to select the function to call # via an expression like $a() case o.functor_expr when Puppet::Pops::Model::QualifiedName # ok when Puppet::Pops::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? Puppet::Pops::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? Puppet::Pops::Model::QualifiedName fail(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function name', :container => o}) end name = name.value # the string function name 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 closure = Puppet::Pops::Evaluator::Closure.new(self, o.lambda, scope) call_function(name, evaluated_arguments, o, scope, &proc_from_closure(closure)) end end private :call_function_with_block # Creates a Proc with an arity count that matches the parameters of the given closure. The arity will # be correct up to 10 parameters and then default to varargs (-1) # def proc_from_closure(closure) return Puppet::Pops::Evaluator::PuppetProc.new(closure) { |*args| closure.call(*args) } unless RUBY_VERSION[0,3] == '1.8' # This code is required since a Proc isn't propagated by reference in Ruby 1.8.x. It produces a standard # Proc that has correct arity as a replacement for the otherwise used PuppetProc # TODO: Remove when Ruby 1.8.x support is dropped arity = closure.parameters.reduce(0) do |memo, param| count = memo + 1 break -count if param.captures_rest || !param.value.nil? count end case arity when 0 proc { || closure.call } when 1 proc { |a| closure.call(a) } when 2 proc { |a, b| closure.call(a, b) } when 3 proc { |a, b, c| closure.call(a, b, c) } when 4 proc { |a, b, c, d| closure.call(a, b, c, d) } when 5 proc { |a, b, c, d, e| closure.call(a, b, c, d, e) } when 6 proc { |a, b, c, d, e, f| closure.call(a, b, c, d, e, f) } when 7 proc { |a, b, c, d, e, f, g| closure.call(a, b, c, d, e, f, g) } when 8 proc { |a, b, c, d, e, f, g, h| closure.call(a, b, c, d, e, f, g, h) } when 9 proc { |a, b, c, d, e, f, g, h, i| closure.call(a, b, c, d, e, f, g, h, i) } when 10 proc { |a, b, c, d, e, f, g, h, i, j| closure.call(a, b, c, d, e, f, g, h, i, j) } when -1 proc { |*v| closure.call(*v) } when -2 proc { |a, *v| closure.call(a, *v) } when -3 proc { |a, b, *v| closure.call(a, b, *v) } when -4 proc { |a, b, c, *v| closure.call(a, b, c, *v) } when -5 proc { |a, b, c, d, *v| closure.call(a, b, c, d, *v) } when -6 proc { |a, b, c, d, e, *v| closure.call(a, b, c, d, e, *v) } when -7 proc { |a, b, c, d, e, f, *v| closure.call(a, b, c, d, e, f, *v) } when -8 proc { |a, b, c, d, e, f, g, *v| closure.call(a, b, c, d, e, f, g, *v) } when -9 proc { |a, b, c, d, e, f, g, h, *v| closure.call(a, b, c, d, e, f,g, h, *v) } when -10 proc { |a, b, c, d, e, f, g, h, i, *v| closure.call(a, b, c, d, e, f,g, h, i, *v) } else proc { |*a| closure.call(*a) } end end private :proc_from_closure # @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. # with_guarded_scope(scope) do test = evaluate(o.left_expr, scope) @migration_checker.report_uc_bareword_type(test, o.left_expr) the_default = nil selected = o.selectors.find do |s| me = unwind_parentheses(s.matching_expr) case me when Puppet::Pops::Model::LiteralDefault the_default = s.value_expr false when Puppet::Pops::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 with_guarded_scope(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 with_guarded_scope(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?(Puppet::Pops::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) "/#{o.source}/" end def string_PAnyType(o, scope) @@type_calculator.string(o) 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 something else, wrap it in an array first. # # 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` # @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) x = [x] unless x.is_a?(Array) || x.is_a?(Hash) 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 calcultor 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? Puppet::Pops::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 else raise ArgumentError.new("Can only append to an Array or a Hash.") 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) @migration_checker.report_option_type_mismatch(left, right, option_expr, o) if right.is_a?(Puppet::Pops::Types::PAnyType) @migration_checker.report_uc_bareword_type(right, o) end @@compare_operator.match(left, right, scope) end def with_guarded_scope(scope) scope_memo = get_scope_nesting_level(scope) begin yield ensure set_scope_nesting_level(scope, scope_memo) end 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[Puppet::Pops::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?(Puppet::Pops::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?(Puppet::Pops::Model::ParenthesizedExpression) unwind_parentheses(o.expr) end private :unwind_parentheses end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/evaluator/external_syntax_support.rb�����������������������������������0000664�0052762�0001160�00000003725�12650174557�025400� 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. # module Puppet::Pops::Evaluator::ExternalSyntaxSupport # TODO: This can be simplified if the Factory directly supporteded hash_of/type_of TYPES = Puppet::Pops::Types::TypeFactory def assert_external_syntax(scope, result, syntax, reference_expr) @@HASH_OF_SYNTAX_CHECKERS ||= TYPES.hash_of(TYPES.type_of(::Puppetx::SYNTAX_CHECKERS_TYPE)) # 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() source_pos = find_closest_positioned(reference_expr) checker.check(result, syntax, acceptor, source_pos) 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 = scope.compiler.injector.lookup(scope, @@HASH_OF_SYNTAX_CHECKERS, ::Puppetx::SYNTAX_CHECKERS) || {} 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-3.8.5/lib/puppet/pops/evaluator/puppet_proc.rb�����������������������������������������������0000664�0052762�0001160�00000004216�12650174557�022710� 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 [Fixnum] 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-3.8.5/lib/puppet/pops/evaluator/relationship_operator.rb�������������������������������������0000664�0052762�0001160�00000015037�12650174557�024767� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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 Puppet::Pops::Evaluator::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 Puppet::Pops::Evaluator::Runtime3Support Issues = Puppet::Pops::Issues 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 = Puppet::Pops::Visitor.new(self, "transform", 1, 1) @type_calculator = Puppet::Pops::Types::TypeCalculator.new() @type_parser = Puppet::Pops::Types::TypeParser.new() tf = Puppet::Pops::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 string must be a type reference in string format # @api private def transform_String(o, scope) assert_catalog_type(@type_parser.parse(o), scope) end # A qualified name is short hand for a class with this name # @api private def transform_QualifiedName(o, scope) Puppet::Pops::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 PHostClassType, 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 = [:'->', :'~>', :'<-', :'<~'] REVERSE_OPERATORS = [:'<-', :'<~'] RELATION_TYPE = { :'->' => :relationship, :'<-' => :relationship, :'~>' => :subscription, :'<~' => :subscription } # 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 {|x| transform(x, 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) }} # Produce the transformed source RHS (if this is a chain, this does not need to be done again) real.slice(1) rescue NotCatalogTypeError => e fail(Issues::ILLEGAL_RELATIONSHIP_OPERAND_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 �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/evaluator/runtime3_converter.rb����������������������������������������0000664�0052762�0001160�00000014342�12650174557�024206� 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 # 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) undef_value end def convert2_NilClass(o, scope, undef_value) :undef 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 alias convert2_String :convert_String def convert_Object(o, scope, undef_value) o end alias :convert2_Object :convert_Object def convert_Array(o, scope, undef_value) o.map {|x| convert2(x, scope, undef_value) } end alias :convert2_Array :convert_Array def convert_Hash(o, scope, undef_value) result = {} o.each {|k,v| result[convert2(k, scope, undef_value)] = convert2(v, scope, undef_value) } result end alias :convert2_Hash :convert_Hash def convert_Regexp(o, scope, undef_value) # Puppet 3x cannot handle parameter values that are reqular expressions. Turn into regexp string in # source form o.inspect end alias :convert2_Regexp :convert_Regexp def convert_Symbol(o, scope, undef_value) case o # Support :undef since it may come from a 3x structure when :undef undef_value # 3x wants undef as either empty string or :undef else o # :default, and all others are verbatim since they are new in future evaluator end end # The :undef symbol should not be converted when nested in arrays or hashes def convert2_Symbol(o, scope, undef_value) o end def convert_PAnyType(o, scope, undef_value) o end alias :convert2_PAnyType :convert_PAnyType 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. Puppet::Resource.new(*catalog_type_to_split_type_title(o)) end alias :convert2_PCatalogEntryType :convert_PCatalogEntryType # 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::PType) ? catalog_type.type : catalog_type case split_type when Puppet::Pops::Types::PHostClassType 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(/^::/, ''), title.nil? ? '' : title] end else raise ArgumentError, "Cannot split the type #{catalog_type.class}, it represents neither a PHostClassType, nor a PResourceType." end end private def initialize @convert_visitor = Puppet::Pops::Visitor.new(self, 'convert', 2, 2) @convert2_visitor = Puppet::Pops::Visitor.new(self, 'convert2', 2, 2) end @@instance = self.new # Converts a nested 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 convert2(o, scope, undef_value) @convert2_visitor.visit_this_2(self, o, scope, undef_value) end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/evaluator/runtime3_support.rb������������������������������������������0000664�0052762�0001160�00000062547�12650174557�023725� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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 Puppet::Pops::Evaluator::Runtime3Support NAME_SPACE_SEPARATOR = '::'.freeze # Fails the evaluation of _semantic_ with a given issue. # # @param issue [Puppet::Pops::Issue] the issue to report # @param semantic [Puppet::Pops::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 [Puppet::Pops::Issue] the issue to report # @param semantic [Puppet::Pops::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 # 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 scope.bound?(name) if Puppet::Parser::Scope::RESERVED_VARIABLE_NAMES.include?(name) fail(Puppet::Pops::Issues::ILLEGAL_RESERVED_ASSIGNMENT, o, {:name => name} ) else fail(Puppet::Pops::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 (x == :undef) ? 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 fail(Puppet::Pops::Issues::UNKNOWN_VARIABLE, o, {:name => name}) end 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) # Yup, 3x uses this method to reset the level, it also supports passing :all to destroy all # ephemeral/local scopes - which is a sure way to create havoc. # scope.unset_ephemeral_var(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?(Puppet::Parser::Collector) || source.is_a?(Puppet::Pops::Evaluator::Collectors::AbstractCollector) # use verbatim - behavior defined by 3x source_resource = source else # transform into the wonderful String representation in 3x type, title = Puppet::Pops::Evaluator::Runtime3Converter.instance.catalog_type_to_split_type_title(source) source_resource = Puppet::Resource.new(type, title) end if target.is_a?(Puppet::Parser::Collector) || target.is_a?(Puppet::Pops::Evaluator::Collectors::AbstractCollector) # use verbatim - behavior defined by 3x target_resource = target else # transform into the wonderful String representation in 3x type, title = Puppet::Pops::Evaluator::Runtime3Converter.instance.catalog_type_to_split_type_title(target) 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) unless n = Puppet::Pops::Utils.to_n(v) fail(Puppet::Pops::Issues::NOT_NUMERIC, o, {:value => v}) end 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) return func.call(scope, *args, &block) end # Call via 3x API if function exists there raise ArgumentError, "Unknown function '#{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 = Puppet::Pops::Evaluator::Runtime3Converter.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) # Call via 4x API if the function exists there loaders = scope.compiler.loaders # find the loader that loaded the code, or use the private_environment_loader (sees env + all modules) adapter = Puppet::Pops::Utils.find_adapter(o, Puppet::Pops::Adapters::LoaderAdapter) loader = adapter.nil? ? loaders.private_environment_loader : adapter.loader if loader && func = loader.load(:function, name) return func.call(scope, *args, &block) end # Call via 3x API if function exists there fail(Puppet::Pops::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 = Puppet::Pops::Evaluator::Runtime3Converter.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 # 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) Puppet::Pops::Evaluator::Runtime3Converter.convert(value, scope, undef_value) end CLASS_STRING = 'class'.freeze def create_resources(o, scope, virtual, exported, type_name, resource_titles, evaluated_parameters) # 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). # resolve in scope. fully_qualified_type, resource_titles = scope.resolve_type_and_titles(type_name, resource_titles) # 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) # Build a resource for each title resource_titles.map do |resource_title| resource = Puppet::Parser::Resource.new( fully_qualified_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 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, true) if fully_qualified_type == CLASS_STRING # Turn the resource into a PType (a reference to a resource type) # weed out nil's resource_to_ptype(resource) end 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?(Puppet::Pops::Types::PResourceType) && r.type_name != 'class' fail(Puppet::Pops::Issues::ILLEGAL_OVERRIDEN_TYPE, o, {:actual => r} ) end resource = Puppet::Parser::Resource.new( r.type_name, 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 ) 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 @migration_checker.report_empty_string_true(value, o) 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) source_pos = Puppet::Pops::Utils.find_closest_positioned(o) return [nil, -1] unless source_pos [source_pos.locator.file, source_pos.line] end def find_closest_positioned(o) return nil if o.nil? || o.is_a?(Puppet::Pops::Model::Program) o.offset.nil? ? find_closest_positioned(o.eContainer) : Puppet::Pops::Adapters::SourcePosAdapter.adapt(o) end # Creates a diagnostic producer def diagnostic_producer Puppet::Pops::Validation::DiagnosticProducer.new( ExceptionRaisingAcceptor.new(), # Raises exception on all issues SeverityProducer.new(), # All issues are errors Puppet::Pops::Model::ModelLabelProvider.new()) end # Configure the severity of failures class SeverityProducer < Puppet::Pops::Validation::SeverityProducer Issues = Puppet::Pops::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 # 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 end end # An acceptor of diagnostics that immediately raises an exception. class ExceptionRaisingAcceptor < Puppet::Pops::Validation::Acceptor def accept(diagnostic) super Puppet::Pops::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 ���������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/functions/�������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020025� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/functions/dispatch.rb��������������������������������������������������0000664�0052762�0001160�00000004754�12650174557�022164� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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 Puppet::Pops::Functions::Dispatch < Puppet::Pops::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 # @api private def initialize(type, method_name, param_names, block_name, injections, weaving, last_captures) @type = type @method_name = method_name @param_names = param_names || [] @block_name = block_name @injections = injections || [] @weaving = weaving @last_captures = last_captures end # @api private def parameter_names @param_names end # @api private def last_captures_rest? !! @last_captures end # @api private def invoke(instance, calling_scope, args, &block) instance.send(@method_name, *weave(calling_scope, args), &block) end # @api private def weave(scope, args) # no need to weave if there are no injections if @injections.empty? args else injector = nil # lazy lookup of injector Puppet.lookup(:injector) new_args = [] @weaving.each do |knit| if knit.is_a?(Array) injection_data = @injections[knit[0]] new_args << case injection_data[3] when :dispatcher_internal # currently only supports :scope injection scope when :producer injector ||= Puppet.lookup(:injector) injector.lookup_producer(scope, injection_data[0], injection_data[2]) else injector ||= Puppet.lookup(:injector) injector.lookup(scope, injection_data[0], injection_data[2]) 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 ��������������������puppet-3.8.5/lib/puppet/pops/functions/dispatcher.rb������������������������������������������������0000664�0052762�0001160�00000005216�12650174557�022505� 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) tc = Puppet::Pops::Types::TypeCalculator actual = tc.infer_set(block_given? ? args + [block] : args) found = @dispatchers.find { |d| tc.callable?(d.type, actual) } if found found.invoke(instance, calling_scope, args, &block) else raise ArgumentError, "function '#{instance.class.name}' called with mis-matched arguments\n#{Puppet::Pops::Evaluator::CallableMismatchDescriber.diff_string(instance.class.name, actual, @dispatchers)}" end end # Adds a regular dispatch for one method 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 names [Array<String>] - array with names matching the number of parameters specified by type (or empty array) # # @api private def add_dispatch(type, method_name, param_names, block_name, injections, weaving, last_captures) @dispatchers << Puppet::Pops::Functions::Dispatch.new(type, method_name, param_names, block_name, injections, weaving, last_captures) 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.copy } # 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 end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/functions/function.rb��������������������������������������������������0000664�0052762�0001160�00000010617�12650174557�022205� 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) self.class.dispatcher.dispatch(self, scope, args, &block) 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 # 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 raise ArgumentError, "Function #{self.class.name}(): cannot call function '#{function_name}' - no loader specified" unless the_loader func = the_loader.load(:function, function_name) return func.call(scope, *args, &block) if func # 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) raise ArgumentError, "Function #{self.class.name}(): cannot call function '#{function_name}' - not found" unless func_3x # 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::Runtime3Converter.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-3.8.5/lib/puppet/pops/issue_reporter.rb������������������������������������������������������0000664�0052762�0001160�00000010574�12650174557�021424� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Pops::IssueReporter # @param acceptor [Puppet::Pops::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 = Puppet::Pops::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 = Puppet::Pops::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 warnings_message = (emit_warnings && warnings.size > 0) ? ", and #{warnings.size} warnings" : "" giving_up_message = "Found #{errors.size} errors#{warnings_message}. Giving up" 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.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) 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), :issue_code => diagnostic.issue.issue_code, :file => file, :line => line, :pos => pos, }) end private_class_method :log_message end ������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/issues.rb��������������������������������������������������������������0000664�0052762�0001160�00000063163�12650174557�017667� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Defines classes to deal with issues, and message formatting and defines constants with Issues. # @api public # module Puppet::Pops::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 Puppet::Pops::Issues::MessageData raise RuntimeError, "Error while reporting issue: #{issue_code}. #{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 # Returns the label provider given as a key in the hash passed to #format. # If given an argument, calls #label on the label provider (caller would otherwise have to # call label.label(it) # def label(it = nil) raise "Label provider key :label must be set to produce the text of the message!" unless @data[:label] it.nil? ? @data[:label] : @data[:label].label(it) end # Returns the label provider given as a key in the hash passed to #format. # def semantic raise "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 "#{label.a_an_uc(semantic)} may not have a name containing a hyphen. The name '#{name}' is not legal" 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" 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 CROSS_SCOPE_ASSIGNMENT = hard_issue :CROSS_SCOPE_ASSIGNMENT, :name do "Illegal attempt to assign to '#{name}'. Cannot assign to variables in other namespaces" 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 '#{label.a_an(semantic)}'. Not an assignable reference" end # Variables are immutable, cannot reassign in the same assignment scope ILLEGAL_REASSIGNMENT = hard_issue :ILLEGAL_REASSIGNMENT, :name do "Cannot reassign variable #{name}" end ILLEGAL_RESERVED_ASSIGNMENT = hard_issue :ILLEGAL_RESERVED_ASSIGNMENT, :name do "Attempt to assign to a reserved variable name: '#{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 '$#{varname}'. Numeric variables are not assignable" 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)" 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 #{label.a_an(semantic)} via [index/key]. Not an assignable reference" 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.puppetlabs.com/remove-plus-equals" end # For unsupported operators (e.g. += and -= in puppet 4). # UNSUPPORTED_OPERATOR = hard_issue :UNSUPPORTED_OPERATOR, :operator do "The operator '#{operator}' is not supported." 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 #{label.a_an(semantic)} is not supported." 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 #{label.a_an(left_value)}." end COMPARISON_NOT_POSSIBLE = hard_issue :COMPARISON_NOT_POSSIBLE, :operator, :left_value, :right_value, :detail do "Comparison of: #{label(left_value)} #{operator} #{label(right_value)}, is not possible. Caused by '#{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}'." end MATCH_NOT_STRING = hard_issue :MATCH_NOT_STRING, :left_value do "Left match operand must result in a String value. Got #{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. #{label.a_an_uc(semantic)} does not produce a value" 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 #{name}. This operator can not be used in #{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*)*$/" 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*$/" 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*)$/" 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" 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" 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)" 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. #{label.a_an_uc(semantic)} is unacceptable as #{feature} in #{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. #{label.a_an_uc(semantic)} did not produce a variable name (String or Numeric)." end # Issues when an expression is used illegaly in a query. # query only supports == and !=, and not <, > etc. # ILLEGAL_QUERY_EXPRESSION = hard_issue :ILLEGAL_QUERY_EXPRESSION do "Illegal query expression. #{label.a_an_uc(semantic)} cannot be used in a query" 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 # 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 #{label.a_an(semantic)}, #{count} values given for max 1" end ILLEGAL_RELATIONSHIP_OPERAND_TYPE = issue :ILLEGAL_RELATIONSHIP_OPERAND_TYPE, :operand do "Illegal relationship operand, can not form a relationship with #{label.a_an(operand)}. A Catalog type is required." end NOT_CATALOG_TYPE = issue :NOT_CATALOG_TYPE, :type do "Illegal relationship operand, can not form a relationship with something of type #{type}. A Catalog type is required." end BAD_STRING_SLICE_ARITY = issue :BAD_STRING_SLICE_ARITY, :actual do "String supports [] with one or two arguments. Got #{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}" end BAD_ARRAY_SLICE_ARITY = issue :BAD_ARRAY_SLICE_ARITY, :actual do "Array supports [] with one or two arguments. Got #{actual}" end BAD_HASH_SLICE_ARITY = issue :BAD_HASH_SLICE_ARITY, :actual do "Hash supports [] with one or more arguments. Got #{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}" end BAD_INTEGER_SLICE_TYPE = issue :BAD_INTEGER_SLICE_TYPE, :actual do "Integer-Type [] requires all arguments to be integers (or default). Got #{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 #{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}" 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}" 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_classes.join(', ')} are" else "#{expected_classes[0]} is" end "#{label.a_an_uc(left_value)}[] cannot use #{actual} where #{expected_text} expected" end BAD_NOT_UNDEF_SLICE_TYPE = issue :BAD_NOT_UNDEF_SLICE_TYPE, :base_type, :actual do "#{base_type}[] argument must be a Type or a String. Got #{actual}" end BAD_TYPE_SLICE_TYPE = issue :BAD_TYPE_SLICE_TYPE, :base_type, :actual do "#{base_type}[] arguments must be types. Got #{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 == 1.0 / 0.0 # Infinity "#{base_type_label}[] accepts #{min} or more arguments. Got #{actual}" elsif max && max != min "#{base_type_label}[] accepts #{min} to #{max} arguments. Got #{actual}" else "#{base_type_label}[] accepts #{min} #{label.plural_s(min, 'argument')}. Got #{actual}" end end BAD_TYPE_SPECIALIZATION = hard_issue :BAD_TYPE_SPECIALIZATION, :type, :message do "Error creating type specialization of #{label.a_an(type)}, #{message}" end ILLEGAL_TYPE_SPECIALIZATION = issue :ILLEGAL_TYPE_SPECIALIZATION, :kind do "Cannot specialize an already specialized #{kind} type" end ILLEGAL_RESOURCE_SPECIALIZATION = issue :ILLEGAL_RESOURCE_SPECIALIZATION, :actual do "First argument to Resource[] must be a resource type or a String. Got #{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. #{label.a_an_uc(name)} cannot be used where a String is expected" end ILLEGAL_DEFINITION_NAME = hard_issue :ILLEGAL_DEFINTION_NAME, :name do "Unacceptable name. The name '#{name}' is unacceptable as the name of #{label.a_an(semantic)}" end CAPTURES_REST_NOT_LAST = hard_issue :CAPTURES_REST_NOT_LAST, :param_name do "Parameter $#{param_name} is not last, and has 'captures rest'" end CAPTURES_REST_NOT_SUPPORTED = hard_issue :CAPTURES_REST_NOT_SUPPORTED, :container, :param_name do "Parameter $#{param_name} has 'captures rest' - not supported in #{label.a_an(container)}" end REQUIRED_PARAMETER_AFTER_OPTIONAL = hard_issue :REQUIRED_PARAMETER_AFTER_OPTIONAL, :param_name do "Parameter $#{param_name} is required but appears after optional parameters" end MISSING_REQUIRED_PARAMETER = hard_issue :MISSING_REQUIRED_PARAMETER, :param_name do "Parameter $#{param_name} is required but no value was given" end NOT_NUMERIC = issue :NOT_NUMERIC, :value do "The value '#{value}' cannot be converted to Numeric." end UNKNOWN_FUNCTION = issue :UNKNOWN_FUNCTION, :name do "Unknown function: '#{name}'." end UNKNOWN_VARIABLE = issue :UNKNOWN_VARIABLE, :name do "Unknown variable: '#{name}'." end RUNTIME_ERROR = issue :RUNTIME_ERROR, :detail do "Error while evaluating #{label.a_an(semantic)}, #{detail}" end UNKNOWN_RESOURCE_TYPE = issue :UNKNOWN_RESOURCE_TYPE, :type_name do "Resource type not found: #{type_name.capitalize}" 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}" end DUPLICATE_TITLE = issue :DUPLICATE_TITLE, :title do "The title '#{title}' has already been used in this resource expression" end DUPLICATE_ATTRIBUTE = issue :DUPLICATE_ATTRIBUE, :attribute do "The attribute '#{attribute}' has already been set in this resource body" 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" end ILLEGAL_TITLE_TYPE_AT = hard_issue :ILLEGAL_TITLE_TYPE_AT, :index, :actual do "Illegal title type at index #{index}. Expected String, got #{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." end UNKNOWN_RESOURCE = issue :UNKNOWN_RESOURCE, :type_name, :title do "Resource not found: #{type_name.capitalize}['#{title}']" end UNKNOWN_RESOURCE_PARAMETER = issue :UNKNOWN_RESOURCE_PARAMETER, :type_name, :title, :param_name do "The resource #{type_name.capitalize}['#{title}'] does not have a parameter called '#{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" end # TODO_HEREDOC EMPTY_HEREDOC_SYNTAX_SEGMENT = issue :EMPTY_HEREDOC_SYNTAX_SEGMENT, :syntax do "Heredoc syntax specification has empty segment between '+' : '#{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 "Use of 'import' has been discontinued in favor of a manifest directory. See http://links.puppetlabs.com/puppet-import-deprecation" end IDEM_EXPRESSION_NOT_LAST = issue :IDEM_EXPRESSION_NOT_LAST do "This #{label.label(semantic)} has no effect. A value-producing expression without other effect may only be placed last in a block/sequence" end IDEM_NOT_ALLOWED_LAST = hard_issue :IDEM_NOT_ALLOWED_LAST, :container do "This #{label.label(semantic)} has no effect. #{label.a_an_uc(container)} can not end with a value-producing expression without other effect" end RESERVED_WORD = hard_issue :RESERVED_WORD, :word do "Use of reserved word: #{word}, must be quoted if intended to be a String value" end FUTURE_RESERVED_WORD = issue :FUTURE_RESERVED_WORD, :word do "Use of future reserved 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 #{label.a_an(semantic)}." end UNMATCHED_SELECTOR = hard_issue :UNMATCHED_SELECTOR, :param_value do "No matching entry for selector parameter with value '#{param_value}'" end ILLEGAL_NODE_INHERITANCE = issue :ILLEGAL_NODE_INHERITANCE do "Node inheritance is not supported in Puppet >= 4.0.0. See http://links.puppetlabs.com/puppet-node-inheritance-deprecation" end ILLEGAL_OVERRIDEN_TYPE = issue :ILLEGAL_OVERRIDEN_TYPE, :actual do "Resource Override can only operate on resources, got: #{label.label(actual)}" end DUPLICATE_PARAMETER = hard_issue :DUPLICATE_PARAMETER, :param_name do "The parameter '#{param_name}' is declared more than once in the parameter list" end RESERVED_PARAMETER = hard_issue :RESERVED_PARAMETER, :container, :param_name do "The parameter $#{param_name} redefines a built in parameter in #{label.the(container)}" end TYPE_MISMATCH = hard_issue :TYPE_MISMATCH, :expected, :actual do "Expected value of type #{expected}, got #{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 SYNTAX_ERROR = hard_issue :SYNTAX_ERROR, :where do "Syntax error at #{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 do 'Illegal number' 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}" end INVALID_OCTAL_NUMBER = hard_issue :INVALID_OCTAL_NUMBER, :value do "Not a valid octal number #{value}" end INVALID_DECIMAL_NUMBER = hard_issue :INVALID_DECIMAL_NUMBER, :value do "Not a valid decimal number #{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}'" end UNCLOSED_QUOTE = hard_issue :UNCLOSED_QUOTE, :after, :followed_by do "Unclosed quote after #{after} followed by '#{followed_by}'" end EPP_INTERNAL_ERROR = hard_issue :EPP_INTERNAL_ERROR, :error do "Internal 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}'" 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}'" 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_MULTIPLE_AT_ESCAPES = hard_issue :HEREDOC_MULTIPLE_AT_ESCAPES, :escapes do "An escape char for @() may only appear once. Got '#{escapes.join(', ')}'" end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/label_provider.rb������������������������������������������������������0000664�0052762�0001160�00000003601�12650174557�021334� 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'. # class 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 *definitie article* (the). def the o "the #{label(o)}" end # Produces a label for the given text with *definitie 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 private # 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 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" end char end def article_for_letter(letter) downcased = letter.downcase if VOWELS.include? downcased AN else A end end end �������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/loader/����������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017263� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/loader/base_loader.rb��������������������������������������������������0000664�0052762�0001160�00000006016�12650174557�022054� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BaseLoader # === # An abstract implementation of Puppet::Pops::Loader::Loader # # A derived class should implement `find(typed_name)` and set entries, and possible handle "miss caching". # # @api private # class Puppet::Pops::Loader::BaseLoader < Puppet::Pops::Loader::Loader # The parent loader attr_reader :parent # An internal name used for debugging and error message purposes attr_reader :loader_name def initialize(parent_loader, loader_name) @parent = parent_loader # the higher priority loader to consult @named_values = {} # hash name => NamedEntry @last_name = nil # the last name asked for (optimization) @last_result = nil # the value of the last name (optimization) @loader_name = loader_name # the name of the loader (not the name-space it is a loader for) 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 typed_name == @last_name @last_result else @last_name = typed_name @last_result = internal_load(typed_name) 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) if entry = @named_values[typed_name] then fail_redefined(entry); end @named_values[typed_name] = Puppet::Pops::Loader::Loader::NamedEntry.new(typed_name, value, origin) end # @api private # def add_entry(type, name, value, origin) set_entry(Puppet::Pops::Loader::Loader::TypedName.new(type, name), value, origin) 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 private def fail_redefine(entry) origin_info = entry.origin ? " Originally set at #{origin_label(entry.origin)}." : "unknown location" raise ArgumentError, "Attempt to redefine entity '#{entry.typed_name}' originally set at #{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) origin.to_s elsif origin.respond_to?(:uri) origin.uri.to_s else origin end 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 @named_values[typed_name] || parent.load_typed(typed_name) || find(typed_name) end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/loader/dependency_loader.rb��������������������������������������������0000664�0052762�0001160�00000004251�12650174557�023257� 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 # Finds name in a loader this loader depends on / can see # def find(typed_name) if typed_name.qualified if loader = index()[typed_name.name_parts[0]] loader.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 def to_s() "(DependencyLoader '#{@loader_name}' [" + @dependency_loaders.map {|loader| loader.to_s }.join(' ,') + "])" end private def index() @index ||= @dependency_loaders.reduce({}) { |index, loader| index[loader.module_name] = loader; index } end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/loader/gem_support.rb��������������������������������������������������0000664�0052762�0001160�00000003730�12650174557�022160� 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}" 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}'" end spec.full_gem_path end end����������������������������������������puppet-3.8.5/lib/puppet/pops/loader/loader.rb�������������������������������������������������������0000664�0052762�0001160�00000013670�12650174557�021066� 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 # class Puppet::Pops::Loader::Loader # 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.new 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.new 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 # 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 # A name/type combination that can be used as a compound hash key # class TypedName attr_reader :type attr_reader :name attr_reader :name_parts # True if name is qualified (more than a single segment) attr_reader :qualified def initialize(type, name) @type = type # relativize the name (get rid of leading ::), and make the split string available @name_parts = name.to_s.split(/::/) @name_parts.shift if name_parts[0].empty? @name = name_parts.join('::') @qualified = name_parts.size > 1 # precompute hash - the name is frozen, so this is safe to do @hash = [self.class, type, @name].hash # Not allowed to have numeric names - 0, 010, 0x10, 1.2 etc if Puppet::Pops::Utils.is_numeric?(@name) raise ArgumentError, "Illegal attempt to use a numeric name '#{name}' at #{origin_label(origin)}." end freeze() end def hash @hash end def ==(o) o.class == self.class && type == o.type && name == o.name end alias eql? == def to_s "#{type}/#{name}" end end end ������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/loader/loader_paths.rb�������������������������������������������������0000664�0052762�0001160�00000006715�12650174557�022267� 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 (3 kinds) # module Puppet::Pops::Loader::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 [FunctionPath4x.new(loader)] 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? root_path = @loader.path @generic_path = (root_path.nil? ? relative_path : File.join(root_path, relative_path)) 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 relative_path() raise NotImplementedError.new end def instantiator() raise NotImplementedError.new end end class RubySmartPath < SmartPath def extension ".rb" 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 class FunctionPath4x < RubySmartPath FUNCTION_PATH_4X = File.join('puppet', 'functions') def relative_path FUNCTION_PATH_4X end def instantiator() Puppet::Pops::Loader::RubyFunctionInstantiator 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 = Puppet::Pops::Loader::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 ���������������������������������������������������puppet-3.8.5/lib/puppet/pops/loader/module_loaders.rb�����������������������������������������������0000664�0052762�0001160�00000026547�12650174557�022625� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� # =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 - this is performed by the # included module Puppet::Pops::Loader::PathBasedInstantatorConfig which knows about the map from type/name to # relative path, and the logic that can instantiate what is expected to be found in the content of that path. # # @api private # module Puppet::Pops::Loader::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.join(File.dirname(__FILE__), '../../..') Puppet::Pops::Loader::ModuleLoaders::FileBased.new(parent_loader, loaders, nil, puppet_lib, 'puppet_system') end def self.module_loader_from(parent_loader, loaders, module_name, module_path) Puppet::Pops::Loader::ModuleLoaders::FileBased.new(parent_loader, loaders, module_name, File.join(module_path, 'lib'), module_name) end class AbstractPathBasedModuleLoader < Puppet::Pops::Loader::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 [Puppet::Pops::Loader] loader with higher priority # @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) super parent_loader, loader_name @module_name = module_name @path = path @smart_paths = Puppet::Pops::Loader::LoaderPaths::SmartPaths.new(self) @loaders = loaders end # Finds typed/named entity in this module # @param typed_name [Puppet::Pops::Loader::TypedName] the type/name to find # @return [Puppet::Pops::Loader::Loader::NamedEntry, nil found/created entry, or nil if not found # def find(typed_name) # Assume it is a global name, and that all parts of the name should be used when looking up name_part_index = 0 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 # Skip the first part of the name when computing the path since the path already contains the name of the # module name_part_index = 1 else # The name is in the global name space. # The only globally name-spaced elements that may be loaded from modules are functions and resource types case typed_name.type when :function when :resource_type 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 = nil if (smart_path = smart_paths.effective_paths(typed_name.type).find do |sp| origin = sp.effective_path(typed_name, name_part_index) existing_path(origin) end) value = smart_path.instantiator.create(self, typed_name, origin, get_contents(origin)) # cache the entry and return it set_entry(typed_name, value, origin) else nil end 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 [Boolean] true if the file exists # def existing_path(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 # 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 privuate loader directly. @private_loader ||= ((module_name.nil? && self) || @loaders.private_loader_for_module(module_name)) 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 [Puppet::Pops::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) 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 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}")) @path_index.merge(found) found end def get_contents(effective_path) Puppet::FileSystem.read(effective_path) 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 Puppet::Pops::Loader::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) @gem_ref = gem_ref super parent_loader, loaders, module_name, gem_dir(gem_ref), loader_name end def to_s() "(ModuleLoader::GemBased '#{loader_name()}' '#{@gem_ref}' [#{module_name()}])" end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/loader/null_loader.rb��������������������������������������������������0000664�0052762�0001160�00000001561�12650174557�022114� 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") @loader_name = loader_name @parent = parent_loader end # Has parent if one was set when constructed def parent @parent end def load_typed(typed_name) if @parent.nil? nil else @parent.load_typed(typed_name) 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-3.8.5/lib/puppet/pops/loader/ruby_function_instantiator.rb�����������������������������������0000664�0052762�0001160�00000003722�12650174557�025302� 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." end created = eval(ruby_code_string, nil, 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 '#{created.class}'" end unless created.name.to_s == typed_name.name() raise ArgumentError, "The code loaded from #{source_ref} produced mis-matched name, expected '#{typed_name.name}', got #{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 # TODO: Cheating wrt. scope - assuming it is found in the context closure_scope = Puppet.lookup(:global_scope) { {} } created.new(closure_scope, loader.private_loader) end end ����������������������������������������������puppet-3.8.5/lib/puppet/pops/loader/simple_environment_loader.rb������������������������������������0000664�0052762�0001160�00000001121�12650174557�025047� 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-3.8.5/lib/puppet/pops/loader/static_loader.rb������������������������������������������������0000664�0052762�0001160�00000005553�12650174557�022436� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� # Static Loader contains constants, basic data types and other types required for the system # to boot. # class Puppet::Pops::Loader::StaticLoader < Puppet::Pops::Loader::Loader attr_reader :loaded def initialize @loaded = {} create_logging_functions() end def load_typed(typed_name) load_constant(typed_name) end def get_entry(typed_name) load_constant(typed_name) 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 private def load_constant(typed_name) @loaded[typed_name] end private # Creates a function for each of the specified log levels # def create_logging_functions() Puppet::Util::Log.levels.each do |level| fc = Puppet::Functions.create_function(level) do # create empty dispatcher to stop it from complaining about missing method since # an override of :call is made instead of using dispatch. dispatch(:log) { } # Logs per the specified level, outputs formatted information for arrays, hashes etc. # Overrides the implementation in Function that uses dispatching. This is not needed here # since it accepts 0-n Object. # define_method(:call) do |scope, *vals| # NOTE: 3x, does this: vals.join(" ") # New implementation uses the evaluator to get proper formatting per type # TODO: uses a fake scope (nil) - fix when :scopes are available via settings mapped = vals.map {|v| Puppet::Pops::Evaluator::EvaluatorImpl.new.string(v, nil) } # 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 => mapped.join(" ")}) end end typed_name = Puppet::Pops::Loader::Loader::TypedName.new(:function, level) # TODO:closure scope is fake (an empty hash) - waiting for new global scope to be available via lookup of :scopes func = fc.new({},self) @loaded[ typed_name ] = Puppet::Pops::Loader::Loader::NamedEntry.new(typed_name, func, __FILE__) end end end �����������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/loader/uri_helper.rb���������������������������������������������������0000664�0052762�0001160�00000001341�12650174557�021746� 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-3.8.5/lib/puppet/pops/loaders.rb�������������������������������������������������������������0000664�0052762�0001160�00000021521�12650174557�017775� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Pops::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 def initialize(environment) # The static loader can only be changed after a reboot @@static_loader ||= Puppet::Pops::Loader::StaticLoader.new() # 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 = create_environment_loader(environment) # 3. module loaders are set up from the create_environment_loader, they register themselves end # Clears the cached static and puppet_system loaders (to enable testing) # def self.clear @@static_loader = nil @puppet_system_loader = nil end def static_loader @@static_loader end def puppet_system_loader @puppet_system_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 private def create_puppet_system_loader() Puppet::Pops::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. # The environment is not a namespace, so give it a nil "module_name" module_name = nil loader_name = "environment:#{environment.name}" loader = Puppet::Pops::Loader::SimpleEnvironmentLoader.new(puppet_system_loader, loader_name) # 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 = Puppet::Pops::Loader::DependencyLoader.new(loader, "environment", @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() environment.modules.each do |puppet_module| # Create data about this module md = LoaderModuleData.new(puppet_module) mr[puppet_module.name] = md md.public_loader = Puppet::Pops::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 :state 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) @state = :initial @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? @state == :resolved 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() @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? return else module_data.private_loader = if module_data.restrict_to_dependencies? 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") Puppet::Pops::Loader::DependencyLoader.new(from_module_data.public_loader, from_module_data.name, all_module_loaders()) end def create_loader_with_only_dependencies_visible(from_module_data) if from_module_data.unmet_dependencies? Puppet.warning("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") end dependency_loaders = from_module_data.dependency_names.collect { |name| @index[name].public_loader } Puppet::Pops::Loader::DependencyLoader.new(from_module_data.public_loader, from_module_data.name, dependency_loaders) end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/migration/�������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020006� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/migration/migration_checker.rb�����������������������������������������0000664�0052762�0001160�00000001624�12650174557�024014� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This class defines the private API of the MigrationChecker support. # @api private # class Puppet::Pops::Migration::MigrationChecker def initialize() end # Produces a hash of available migrations; a map from a symbolic name in string form to a brief description. def available_migrations() { '3.8/4.0' => '3.8 future parser to 4.0 language migrations'} end # For 3.8/4.0 def report_ambiguous_integer(o) end # For 3.8/4.0 def report_ambiguous_float(o) end # For 3.8/4.0 def report_empty_string_true(value, o) end # For 3.8/4.0 def report_uc_bareword_type(value, o) end # For 3.8/4.0 def report_equality_type_mismatch(left, right, o) end # For 3.8/4.0 def report_option_type_mismatch(test_value, option_value, option_expr, matching_expr) end # For 3.8/4.0 def report_in_expression(o) end # For 3.8/4.0 def report_array_last_in_block(o) end end ������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/model/�����������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017115� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/model/ast_transformer.rb�����������������������������������������������0000664�0052762�0001160�00000050437�12650174557�022665� 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 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 = {} source_pos = Puppet::Pops::Utils.find_closest_positioned(o) if source_pos pos[:line] = source_pos.line pos[:pos] = source_pos.pos end pos[:file] = @source_file if @source_file hash = hash.merge(pos) end hash end # Transforms pops expressions into AST 3.1 statements/expressions def transform(o) begin @@transform_visitor.visit_this(self,o) rescue StandardError => e loc_data = {} merge_location(loc_data, o) raise Puppet::ParseError.new("Error while transforming to Puppet 3 AST: #{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(self, o) end # Transforms pops expressions into AST 3.1 hostnames def hostname(o) @@hostname_transform_visitor.visit_this(self, o) end def transform_LiteralFloat(o) # Numbers are Names in the AST !! (Name a.k.a BareWord) ast o, AST::Name, :value => o.value.to_s end def transform_LiteralInteger(o) s = 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 # Numbers are Names in the AST !! (Name a.k.a BareWord) ast o, AST::Name, :value => s end # Transforms all literal values to string (override for those that should not be AST::String) # def transform_LiteralValue(o) ast o, AST::String, :value => o.value.to_s end def transform_LiteralBoolean(o) ast o, AST::Boolean, :value => o.value end def transform_Factory(o) transform(o.current) end def transform_ArithmeticExpression(o) ast o, AST::ArithmeticOperator2, :lval => transform(o.left_expr), :rval=>transform(o.right_expr), :operator => o.operator.to_s end def transform_Array(o) ast nil, AST::ASTArray, :children => o.collect {|x| transform(x) } end # Puppet AST only allows: # * variable[expression] => Hasharray Access # * NAME [expressions] => Resource Reference(s) # * type [epxressions] => Resource Reference(s) # * HashArrayAccesses[expression] => HasharrayAccesses # # i.e. it is not possible to do `func()[3]`, `[1,2,3][$x]`, `{foo=>10, bar=>20}[$x]` etc. since # LHS is not an expression # # Validation for 3.x semantics should validate the illegal cases. This transformation may fail, # or ignore excess information if the expressions are not correct. # This means that the transformation does not have to evaluate the lhs to detect the target expression. # # Hm, this seems to have changed, the LHS (variable) is evaluated if evaluateable, else it is used as is. # def transform_AccessExpression(o) case o.left_expr when Model::QualifiedName ast o, AST::ResourceReference, :type => o.left_expr.value, :title => transform(o.keys) when Model::QualifiedReference ast o, AST::ResourceReference, :type => o.left_expr.value, :title => transform(o.keys) when Model::VariableExpression ast o, AST::HashOrArrayAccess, :variable => transform(o.left_expr), :key => transform(o.keys()[0]) else ast o, AST::HashOrArrayAccess, :variable => transform(o.left_expr), :key => transform(o.keys()[0]) end end # Puppet AST has a complicated structure # LHS can not be an expression, it must be a type (which is downcased). # type = a downcased QualifiedName # def transform_CollectExpression(o) raise "LHS is not a type" unless o.type_expr.is_a? Model::QualifiedReference type = o.type_expr.value().downcase() args = { :type => type } # This somewhat peculiar encoding is used by the 3.1 AST. query = transform(o.query) if query.is_a? Symbol args[:form] = query else args[:form] = query.form args[:query] = query query.type = type end if o.operations.size > 0 args[:override] = transform(o.operations) end ast o, AST::Collection, args end def transform_EppExpression(o) # TODO: Not supported in 3x TODO_EPP parameters = o.parameters.collect {|p| transform(p) } args = { :parameters => parameters } args[:children] = transform(o.body) unless is_nop?(o.body) Puppet::Parser::AST::Epp.new(merge_location(args, o)) end def transform_ExportedQuery(o) if is_nop?(o.expr) result = :exported else result = query(o.expr) result.form = :exported end result end def transform_VirtualQuery(o) if is_nop?(o.expr) result = :virtual else result = query(o.expr) result.form = :virtual end result 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: "+o.class.name end # Puppet AST only allows == and !=, and left expr is restricted, but right value is an expression # def query_ComparisonExpression(o) if [:'==', :'!='].include? o.operator ast o, AST::CollExpr, :test1 => query(o.left_expr), :oper => o.operator.to_s, :test2 => transform(o.right_expr) else raise "Not a valid comparison operator in a collection query: " + o.operator.to_s end end def query_AndExpression(o) ast o, AST::CollExpr, :test1 => query(o.left_expr), :oper => 'and', :test2 => query(o.right_expr) end def query_OrExpression(o) ast o, AST::CollExpr, :test1 => query(o.left_expr), :oper => 'or', :test2 => query(o.right_expr) end def query_ParenthesizedExpression(o) result = query(o.expr) # produces CollExpr result.parens = true result end def query_VariableExpression(o) transform(o) end def query_QualifiedName(o) transform(o) end def query_LiteralNumber(o) transform(o) # number to string in correct radix end def query_LiteralString(o) transform(o) end def query_LiteralBoolean(o) transform(o) end def transform_QualifiedName(o) ast o, AST::Name, :value => o.value end def transform_QualifiedReference(o) ast o, AST::Type, :value => o.value end def transform_ComparisonExpression(o) ast o, AST::ComparisonOperator, :operator => o.operator.to_s, :lval => transform(o.left_expr), :rval => transform(o.right_expr) end def transform_AndExpression(o) ast o, AST::BooleanOperator, :operator => 'and', :lval => transform(o.left_expr), :rval => transform(o.right_expr) end def transform_OrExpression(o) ast o, AST::BooleanOperator, :operator => 'or', :lval => transform(o.left_expr), :rval => transform(o.right_expr) end def transform_InExpression(o) ast o, AST::InOperator, :lval => transform(o.left_expr), :rval => transform(o.right_expr) end # Assignment in AST 3.1 is to variable or hasharray accesses !!! See Bug #16116 def transform_AssignmentExpression(o) args = {:value => transform(o.right_expr) } case o.operator when :'+=' args[:append] = true when :'=' else raise "The operator #{o.operator} is not supported by Puppet 3." end args[:name] = case o.left_expr when Model::VariableExpression ast o, AST::Name, {:value => o.left_expr.expr.value } when Model::AccessExpression transform(o.left_expr) else raise "LHS is not an expression that can be assigned to" end ast o, AST::VarDef, args end # Produces (name => expr) or (name +> expr) def transform_AttributeOperation(o) args = { :value => transform(o.value_expr) } args[:add] = true if o.operator == :'+>' args[:param] = o.attribute_name ast o, AST::ResourceParam, args end def transform_LiteralList(o) # Uses default transform of Ruby Array to ASTArray transform(o.values) end # Literal hash has strange behavior in Puppet 3.1. See Bug #19426, and this implementation is bug # compatible def transform_LiteralHash(o) if o.entries.size == 0 ast o, AST::ASTHash, {:value=> {}} else value = {} o.entries.each {|x| value.merge! transform(x) } ast o, AST::ASTHash, {:value=> value} end end # Transforms entry into a hash (they are later merged with strange effects: Bug #19426). # Puppet 3.x only allows: # * NAME # * quotedtext # As keys (quoted text can be an interpolated string which is compared as a key in a less than satisfactory way). # def transform_KeyedEntry(o) value = transform(o.value) key = case o.key when Model::QualifiedName o.key.value when Model::LiteralString transform o.key when Model::LiteralNumber transform o.key when Model::ConcatenatedString transform o.key else raise "Illegal hash key expression of type (#{o.key.class})" end {key => value} end def transform_MatchExpression(o) ast o, AST::MatchOperator, :operator => o.operator.to_s, :lval => transform(o.left_expr), :rval => transform(o.right_expr) end def transform_LiteralString(o) ast o, AST::String, :value => o.value end def transform_LambdaExpression(o) astargs = { :parameters => o.parameters.collect {|p| transform(p) } } astargs.merge!({ :children => transform(o.body) }) if o.body # do not want children if it is nil/nop ast o, AST::Lambda, astargs end def transform_LiteralDefault(o) ast o, AST::Default, :value => :default end def transform_LiteralUndef(o) ast o, AST::Undef, :value => :undef end def transform_LiteralRegularExpression(o) ast o, AST::Regex, :value => o.value end def transform_Nop(o) ast o, AST::Nop end # In the 3.1. grammar this is a hash that is merged with other elements to form a method call # Also in 3.1. grammar there are restrictions on the LHS (that are only there for grammar issues). # def transform_NamedAccessExpression(o) receiver = transform(o.left_expr) name = o.right_expr raise "Unacceptable function/method name" unless name.is_a? Model::QualifiedName {:receiver => receiver, :name => name.value} end def transform_NilClass(o) ast o, AST::Nop, {} end def transform_NotExpression(o) ast o, AST::Not, :value => transform(o.expr) end def transform_VariableExpression(o) # assumes the expression is a QualifiedName ast o, AST::Variable, :value => o.expr.value end # In Puppet 3.1, the ConcatenatedString is responsible for the evaluation and stringification of # expression segments. Expressions and Strings are kept in an array. def transform_TextExpression(o) transform(o.expr) end def transform_UnaryMinusExpression(o) ast o, AST::Minus, :value => transform(o.expr) end # Puppet 3.1 representation of a BlockExpression is an AST::Array - this makes it impossible to differentiate # between a LiteralArray and a Sequence. (Should it return the collected array, or the last expression?) # (A BlockExpression has now been introduced in the AST to solve this). # def transform_BlockExpression(o) children = [] # remove nops resulting from import o.statements.each {|s| r = transform(s); children << r unless is_nop?(r) } ast o, AST::BlockExpression, :children => children # o.statements.collect {|s| transform(s) } end # Interpolated strings are kept in an array of AST (string or other expression). def transform_ConcatenatedString(o) ast o, AST::Concat, :value => o.segments.collect {|x| transform(x)} end def transform_HostClassDefinition(o) parameters = o.parameters.collect {|p| transform(p) } args = { :arguments => parameters, :parent => o.parent_class, } args[:code] = transform(o.body) unless is_nop?(o.body) Puppet::Parser::AST::Hostclass.new(o.name, merge_location(args, o)) end def transform_HeredocExpression(o) # TODO_HEREDOC Not supported in 3x args = {:syntax=> o.syntax(), :expr => transform(o.text_expr()) } Puppet::Parser::AST::Heredoc.new(merge_location(args, o)) end def transform_NodeDefinition(o) # o.host_matches are expressions, and 3.1 AST requires special object AST::HostName # where a HostName is one of NAME, STRING, DEFAULT or Regexp - all of these are strings except regexp # args = { :code => transform(o.body) } args[:parent] = hostname(o.parent) unless is_nop?(o.parent) if(args[:parent].is_a?(Array)) raise "Illegal expression - unacceptable as a node parent" end Puppet::Parser::AST::Node.new(hostname(o.host_matches), merge_location(args, o)) 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_RelationshipExpression(o) Puppet::Parser::AST::Relationship.new(transform(o.left_expr), transform(o.right_expr), o.operator.to_s, merge_location({}, o)) end def transform_RenderStringExpression(o) # TODO_EPP Not supported in 3x ast o, AST::RenderString, :value => o.value end def transform_RenderExpression(o) # TODO_EPP Not supported in 3x ast o, AST::RenderExpression, :value => transform(o.expr) end def transform_ResourceTypeDefinition(o) parameters = o.parameters.collect {|p| transform(p) } args = { :arguments => parameters } args[:code] = transform(o.body) unless is_nop?(o.body) Puppet::Parser::AST::Definition.new(o.name, merge_location(args, o)) end # Transformation of ResourceOverrideExpression is slightly more involved than a straight forward # transformation. # A ResourceOverrideExppression has "resources" which should be an AccessExpression # on the form QualifiedName[expressions], or QualifiedReference[expressions] to be valid. # It also has a set of attribute operations. # # The AST equivalence is an AST::ResourceOverride with a ResourceReference as its LHS, and # a set of Parameters. # ResourceReference has type as a string, and the expressions representing # the "titles" to be an ASTArray. # def transform_ResourceOverrideExpression(o) raise "Unsupported transformation - use the new evaluator" end # Parameter is a parameter in a definition of some kind. # It is transformed to an array on the form `[name]´, or `[name, value]´. def transform_Parameter(o) if o.value [o.name, transform(o.value)] else [o.name] end end # For non query expressions, parentheses can be dropped in the resulting AST. def transform_ParenthesizedExpression(o) transform(o.expr) end def transform_Program(o) transform(o.body) end def transform_IfExpression(o) args = { :test => transform(o.test), :statements => transform(o.then_expr) } args[:else] = transform(o.else_expr) # Tests say Nop should be there (unless is_nop? o.else_expr), probably not needed ast o, AST::IfStatement, args end # Unless is not an AST object, instead an AST::IfStatement is used with an AST::Not around the test # def transform_UnlessExpression(o) args = { :test => ast(o, AST::Not, :value => transform(o.test)), :statements => transform(o.then_expr) } # AST 3.1 does not allow else on unless in the grammar, but it is ok since unless is encoded as an if !x args.merge!({:else => transform(o.else_expr)}) unless is_nop?(o.else_expr) ast o, AST::IfStatement, args end # Puppet 3.1 AST only supports calling a function by name (it is not possible to produce a function # that is then called). # rval_required (for an expression) # functor_expr (lhs - the "name" expression) # arguments - list of arguments # def transform_CallNamedFunctionExpression(o) name = o.functor_expr raise "Unacceptable expression for name of function" unless name.is_a? Model::QualifiedName args = { :name => name.value, :arguments => transform(o.arguments), :ftype => o.rval_required ? :rvalue : :statement } args[:pblock] = transform(o.lambda) if o.lambda ast o, AST::Function, args end # Transformation of CallMethodExpression handles a NamedAccessExpression functor and # turns this into a 3.1 AST::MethodCall. # def transform_CallMethodExpression(o) name = o.functor_expr raise "Unacceptable expression for name of function" unless name.is_a? Model::NamedAccessExpression # transform of NamedAccess produces a hash, add arguments to it astargs = transform(name).merge(:arguments => transform(o.arguments)) astargs.merge!(:lambda => transform(o.lambda)) if o.lambda # do not want a Nop as the lambda ast o, AST::MethodCall, astargs end def transform_CaseExpression(o) # Expects expression, AST::ASTArray of AST ast o, AST::CaseStatement, :test => transform(o.test), :options => transform(o.options) end def transform_CaseOption(o) ast o, AST::CaseOpt, :value => transform(o.values), :statements => transform(o.then_expr) end def transform_ResourceBody(o) raise "Unsupported transformation - use the new evaluator" end def transform_ResourceDefaultsExpression(o) raise "Unsupported transformation - use the new evaluator" end # Transformation of ResourceExpression requires calling a method on the resulting # AST::Resource if it is virtual or exported # def transform_ResourceExpression(o) raise "Unsupported transformation - use the new evaluator" end # Transformation of SelectorExpression is limited to certain types of expressions. # This is probably due to constraints in the old grammar rather than any real concerns. def transform_SelectorExpression(o) case o.left_expr when Model::CallNamedFunctionExpression when Model::AccessExpression when Model::VariableExpression when Model::ConcatenatedString else raise "Unacceptable select expression" unless o.left_expr.kind_of? Model::Literal end ast o, AST::Selector, :param => transform(o.left_expr), :values => transform(o.selectors) end def transform_SelectorEntry(o) ast o, AST::ResourceParam, :param => transform(o.matching_expr), :value => transform(o.value_expr) end def transform_Object(o) raise "Unacceptable transform - found an Object without a rule: #{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 end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/model/ast_tree_dumper.rb�����������������������������������������������0000664�0052762�0001160�00000022330�12650174557�022625� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast' # 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::AstTreeDumper < Puppet::Pops::Model::TreeDumper AST = Puppet::Parser::AST Model = Puppet::Pops::Model 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_Expression(o) "(pops-expression #{Puppet::Pops::Model::ModelTreeDumper.new().dump(o.value)})" end def dump_Factory o do_dump(o.current) end def dump_ArithmeticOperator o [o.operator.to_s, do_dump(o.lval), do_dump(o.rval)] end def dump_Relationship o [o.arrow.to_s, do_dump(o.left), do_dump(o.right)] end # Hostname is tricky, it is either a bare word, a string, or default, or regular expression # Least evil, all strings except default are quoted def dump_HostName o result = do_dump o.value unless o.value.is_a? AST::Regex result = result == "default" ? ":default" : "'#{result}'" end result end # x[y] prints as (slice x y) def dump_HashOrArrayAccess o var = o.variable.is_a?(String) ? "$#{o.variable}" : do_dump(o.variable) ["slice", var, do_dump(o.key)] end # The AST Collection knows about exported or virtual query, not the query. def dump_Collection o result = ["collect", do_dump(o.type), :indent, :break] if o.form == :virtual q = ["<| |>"] else q = ["<<| |>>"] end q << do_dump(o.query) unless is_nop?(o.query) q << :indent result << q o.override do |ao| result << :break << do_dump(ao) end result += [:dedent, :dedent ] result end def dump_CollExpr o operator = case o.oper when 'and' '&&' when 'or' '||' else o.oper end [operator, do_dump(o.test1), do_dump(o.test2)] end def dump_ComparisonOperator o [o.operator.to_s, do_dump(o.lval), do_dump(o.rval)] end def dump_Boolean o o.to_s end def dump_BooleanOperator o operator = o.operator == 'and' ? '&&' : '||' [operator, do_dump(o.lval), do_dump(o.rval)] end def dump_InOperator o ["in", do_dump(o.lval), do_dump(o.rval)] end # $x = ... # $x += ... # def dump_VarDef o operator = o.append ? "+=" : "=" [operator, '$' + do_dump(o.name), do_dump(o.value)] end # Produces (name => expr) or (name +> expr) def dump_ResourceParam o operator = o.add ? "+>" : "=>" [do_dump(o.param), operator, do_dump(o.value)] end def dump_Array o o.collect {|e| do_dump(e) } end def dump_ASTArray o ["[]"] + o.children.collect {|x| do_dump(x)} end def dump_ASTHash o ["{}"] + o.value.sort_by{|k,v| k.to_s}.collect {|x| [do_dump(x[0]), do_dump(x[1])]} # ["{}"] + o.value.collect {|x| [do_dump(x[0]), do_dump(x[1])]} end def dump_MatchOperator o [o.operator.to_s, do_dump(o.lval), do_dump(o.rval)] end # Dump a Ruby String in single quotes unless it is a number. def dump_String o if o.is_a? String o # A Ruby String, not quoted elsif Puppet::Pops::Utils.to_n(o.value) o.value # AST::String that is a number without quotes else "'#{o.value}'" # AST::String that is not a number end end def dump_Lambda o result = ["lambda"] result << ["parameters"] + o.parameters.collect {|p| _dump_ParameterArray(p) } if o.parameters.size() > 0 if o.children == [] result << [] # does not have a lambda body else result << do_dump(o.children) end result end def dump_Default o ":default" end def dump_Undef o ":undef" end # Note this is Regex (the AST kind), not Ruby Regexp def dump_Regex o "/#{o.value.source}/" end def dump_Nop o ":nop" end def dump_NilClass o "()" end def dump_Not o ['!', dump(o.value)] end def dump_Variable o "$#{dump(o.value)}" end def dump_Minus o ['-', do_dump(o.value)] end def dump_BlockExpression o ["block"] + o.children.collect {|x| do_dump(x) } end # Interpolated strings are shown as (cat seg0 seg1 ... segN) def dump_Concat o ["cat"] + o.value.collect {|x| x.is_a?(AST::String) ? " "+do_dump(x) : ["str", do_dump(x)]} end def dump_Hostclass o # ok, this is kind of crazy stuff in the AST, information in a context instead of in AST, and # parameters are in a Ruby Array with each parameter being an Array... # context = o.context args = context[:arguments] parent = context[:parent] result = ["class", o.name] result << ["inherits", parent] if parent result << ["parameters"] + args.collect {|p| _dump_ParameterArray(p) } if args && args.size() > 0 if is_nop?(o.code) result << [] else result << do_dump(o.code) end result end def dump_Name o o.value end def dump_Node o context = o.context parent = context[:parent] code = context[:code] result = ["node"] result << ["matches"] + o.names.collect {|m| do_dump(m) } result << ["parent", do_dump(parent)] if !is_nop?(parent) if is_nop?(code) result << [] else result << do_dump(code) end result end def dump_Definition o # ok, this is even crazier that Hostclass. The name of the define does not have an accessor # and some things are in the context (but not the name). Parameters are called arguments and they # are in a Ruby Array where each parameter is an array of 1 or 2 elements. # context = o.context name = o.instance_variable_get("@name") args = context[:arguments] code = context[:code] result = ["define", name] result << ["parameters"] + args.collect {|p| _dump_ParameterArray(p) } if args && args.size() > 0 if is_nop?(code) result << [] else result << do_dump(code) end result end def dump_ResourceReference o result = ["slice", do_dump(o.type)] if o.title.children.size == 1 result << do_dump(o.title[0]) else result << do_dump(o.title.children) end result end def dump_ResourceOverride o result = ["override", do_dump(o.object), :indent] o.parameters.each do |p| result << :break << do_dump(p) end result << :dedent result end # Puppet AST encodes a parameter as a one or two slot Array. # This is not a polymorph dump method. # def _dump_ParameterArray o if o.size == 2 ["=", o[0], do_dump(o[1])] else o[0] end end def dump_IfStatement o result = ["if", do_dump(o.test), :indent, :break, ["then", :indent, do_dump(o.statements), :dedent]] result += [:break, ["else", :indent, do_dump(o.else), :dedent], :dedent] unless is_nop? o.else result end # Produces (invoke name args...) when not required to produce an rvalue, and # (call name args ... ) otherwise. # def dump_Function o # somewhat ugly as Function hides its "ftype" instance variable result = [o.instance_variable_get("@ftype") == :rvalue ? "call" : "invoke", do_dump(o.name)] o.arguments.collect {|a| result << do_dump(a) } result << do_dump(o.pblock) if o.pblock result end def dump_MethodCall o # somewhat ugly as Method call (does the same as function) and hides its "ftype" instance variable result = [o.instance_variable_get("@ftype") == :rvalue ? "call-method" : "invoke-method", [".", do_dump(o.receiver), do_dump(o.name)]] o.arguments.collect {|a| result << do_dump(a) } result << do_dump(o.lambda) if o.lambda result end def dump_CaseStatement o result = ["case", do_dump(o.test), :indent] o.options.each do |s| result << :break << do_dump(s) end result << :dedent end def dump_CaseOpt o result = ["when"] result << o.value.collect {|x| do_dump(x) } # A bit of trickery to get it into the same shape as Pops output if is_nop?(o.statements) result << ["then", []] # Puppet AST has a nop if there is no body else result << ["then", do_dump(o.statements) ] end result end def dump_ResourceInstance o result = [do_dump(o.title), :indent] o.parameters.each do |p| result << :break << do_dump(p) end result << :dedent result end def dump_ResourceDefaults o result = ["resource-defaults", do_dump(o.type), :indent] o.parameters.each do |p| result << :break << do_dump(p) end result << :dedent result end def dump_Resource o if o.exported form = 'exported-' elsif o.virtual form = 'virtual-' else form = '' end result = [form+"resource", do_dump(o.type), :indent] o.instances.each do |b| result << :break << do_dump(b) end result << :dedent result end def dump_Selector o values = o.values values = [values] unless values.instance_of? AST::ASTArray or values.instance_of? Array ["?", do_dump(o.param)] + values.collect {|x| do_dump(x) } end def dump_Object o ['dev-error-no-polymorph-dump-for:', o.class.to_s, o.to_s] end def is_nop? o o.nil? || o.is_a?(Model::Nop) || o.is_a?(AST::Nop) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/model/factory.rb�������������������������������������������������������0000664�0052762�0001160�00000073354�12650174557�021126� 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)... # class Puppet::Pops::Model::Factory Model = Puppet::Pops::Model attr_accessor :current alias_method :model, :current # Shared build_visitor, since there are many instances of Factory being used @@build_visitor = Puppet::Pops::Visitor.new(self, "build") @@interpolation_visitor = Puppet::Pops::Visitor.new(self, "interpolate") # Initialize a factory with a single object, or a class with arguments applied to build of # created instance # def initialize o, *args @current = case o when Model::PopsObject o when Puppet::Pops::Model::Factory o.current else build(o, *args) end end # Polymorphic build def build(o, *args) begin @@build_visitor.visit_this(self, o, *args) rescue =>e # debug here when in trouble... raise e end end # Polymorphic interpolate def interpolate() begin @@interpolation_visitor.visit_this_0(self, current) rescue =>e # debug here when in trouble... raise e end end # Building of Model classes def build_ArithmeticExpression(o, op, a, b) o.operator = op build_BinaryExpression(o, a, b) end def build_AssignmentExpression(o, op, a, b) o.operator = op build_BinaryExpression(o, a, b) end def build_AttributeOperation(o, name, op, value) o.operator = op o.attribute_name = name.to_s # BOOLEAN is allowed in the grammar o.value_expr = build(value) o end def build_AttributesOperation(o, value) o.expr = build(value) o end def build_AccessExpression(o, left, *keys) o.left_expr = to_ops(left) keys.each {|expr| o.addKeys(to_ops(expr)) } o end def build_BinaryExpression(o, left, right) o.left_expr = to_ops(left) o.right_expr = to_ops(right) o end def build_BlockExpression(o, *args) args.each {|expr| o.addStatements(to_ops(expr)) } o end def build_CollectExpression(o, type_expr, query_expr, attribute_operations) o.type_expr = to_ops(type_expr) o.query = build(query_expr) attribute_operations.each {|op| o.addOperations(build(op)) } o end def build_ComparisonExpression(o, op, a, b) o.operator = op build_BinaryExpression(o, a, b) end def build_ConcatenatedString(o, *args) args.each {|expr| o.addSegments(build(expr)) } o end def build_CreateTypeExpression(o, name, super_name = nil) o.name = name o.super_name = super_name o end def build_CreateEnumExpression(o, *args) o.name = args.slice(0) if args.size == 2 o.values = build(args.last) o end def build_CreateAttributeExpression(o, name, datatype_expr) o.name = name o.type = to_ops(datatype_expr) o end def build_HeredocExpression(o, name, expr) o.syntax = name o.text_expr = build(expr) o end # @param name [String] a valid classname # @param parameters [Array<Model::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 [Model::HostClassDefinition] configured from the parameters # def build_HostClassDefinition(o, name, parameters, parent_class_name, body) build_NamedDefinition(o, name, parameters, body) o.parent_class = parent_class_name if parent_class_name o end def build_ResourceOverrideExpression(o, resources, attribute_operations) o.resources = build(resources) attribute_operations.each {|ao| o.addOperations(build(ao)) } o end def build_ReservedWord(o, name, future) o.word = name o.future = future o end def build_KeyedEntry(o, k, v) o.key = to_ops(k) o.value = to_ops(v) o end def build_LiteralHash(o, *keyed_entries) keyed_entries.each {|entry| o.addEntries build(entry) } o end def build_LiteralList(o, *values) values.each {|v| o.addValues build(v) } o end def build_LiteralFloat(o, val) o.value = val o end def build_LiteralInteger(o, val, radix) o.value = val o.radix = radix o end def build_IfExpression(o, t, ift, els) o.test = build(t) o.then_expr = build(ift) o.else_expr= build(els) o end def build_MatchExpression(o, op, a, b) o.operator = op build_BinaryExpression(o, a, b) end # Builds body :) from different kinds of input # @overload f_build_body(nothing) # @param nothing [nil] unchanged, produces nil # @overload f_build_body(array) # @param array [Array<Expression>] turns into a BlockExpression # @overload f_build_body(expr) # @param expr [Expression] produces the given expression # @overload f_build_body(obj) # @param obj [Object] produces the result of calling #build with body as argument def f_build_body(body) case body when NilClass nil when Array Puppet::Pops::Model::Factory.new(Model::BlockExpression, *body) else build(body) end end def build_LambdaExpression(o, parameters, body) parameters.each {|p| o.addParameters(build(p)) } b = f_build_body(body) o.body = to_ops(b) if b o end def build_NamedDefinition(o, name, parameters, body) parameters.each {|p| o.addParameters(build(p)) } b = f_build_body(body) o.body = b.current if b o.name = name o end # @param o [Model::NodeDefinition] # @param hosts [Array<Expression>] host matches # @param parent [Expression] parent node matcher # @param body [Object] see {#f_build_body} def build_NodeDefinition(o, hosts, parent, body) hosts.each {|h| o.addHost_matches(build(h)) } o.parent = build(parent) if parent # no nop here b = f_build_body(body) o.body = b.current if b o end def build_Parameter(o, name, expr) o.name = name o.value = build(expr) if expr # don't build a nil/nop o end def build_QualifiedReference(o, name) o.value = name.to_s.downcase o end def build_RelationshipExpression(o, op, a, b) o.operator = op build_BinaryExpression(o, a, b) end def build_ResourceExpression(o, type_name, bodies) o.type_name = build(type_name) bodies.each {|b| o.addBodies(build(b)) } o end def build_RenderStringExpression(o, string) o.value = string; o end def build_ResourceBody(o, title_expression, attribute_operations) o.title = build(title_expression) attribute_operations.each {|ao| o.addOperations(build(ao)) } o end def build_ResourceDefaultsExpression(o, type_ref, attribute_operations) o.type_ref = build(type_ref) attribute_operations.each {|ao| o.addOperations(build(ao)) } o end def build_SelectorExpression(o, left, *selectors) o.left_expr = to_ops(left) selectors.each {|s| o.addSelectors(build(s)) } o 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) o.expr = build(expression) o.offset = token.offset o.length = token.length locator = token.locator o.locator = locator o.leading_line_count = locator.leading_line_count o.leading_line_offset = locator.leading_line_offset # Index is held in sublocator's parent locator - needed to be able to reconstruct o.line_offsets = locator.locator.line_index o end def build_SelectorEntry(o, matching, value) o.matching_expr = build(matching) o.value_expr = build(value) o end def build_QueryExpression(o, expr) ops = to_ops(expr) o.expr = ops unless Puppet::Pops::Model::Factory.nop? ops o end def build_UnaryExpression(o, expr) ops = to_ops(expr) o.expr = ops unless Puppet::Pops::Model::Factory.nop? ops o end def build_Program(o, body, definitions, locator) o.body = to_ops(body) # non containment definitions.each { |d| o.addDefinitions(d) } o.source_ref = locator.file o.source_text = locator.string o.line_offsets = locator.line_index o.locator = locator o end def build_QualifiedName(o, name) o.value = name.to_s o 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 # Puppet::Pops::Model::Factory helpers def f_build_unary(klazz, expr) Puppet::Pops::Model::Factory.new(build(klazz.new, expr)) end def f_build_binary_op(klazz, op, left, right) Puppet::Pops::Model::Factory.new(build(klazz.new, op, left, right)) end def f_build_binary(klazz, left, right) Puppet::Pops::Model::Factory.new(build(klazz.new, left, right)) end def f_build_vararg(klazz, left, *arg) Puppet::Pops::Model::Factory.new(build(klazz.new, left, *arg)) end def f_arithmetic(op, r) f_build_binary_op(Model::ArithmeticExpression, op, current, r) end def f_comparison(op, r) f_build_binary_op(Model::ComparisonExpression, op, current, r) end def f_match(op, r) f_build_binary_op(Model::MatchExpression, op, current, r) end # Operator helpers def in(r) f_build_binary(Model::InExpression, current, r); end def or(r) f_build_binary(Model::OrExpression, current, r); end def and(r) f_build_binary(Model::AndExpression, current, r); end def not(); f_build_unary(Model::NotExpression, self); end def minus(); f_build_unary(Model::UnaryMinusExpression, self); end def unfold(); f_build_unary(Model::UnfoldExpression, self); end def text(); f_build_unary(Model::TextExpression, self); end def var(); f_build_unary(Model::VariableExpression, self); end def [](*r); f_build_vararg(Model::AccessExpression, current, *r); end def dot r; f_build_binary(Model::NamedAccessExpression, current, 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 == 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(Model::ParenthesizedExpression, current); end def relop op, r f_build_binary_op(Model::RelationshipExpression, op.to_sym, current, r) end def select *args Puppet::Pops::Model::Factory.new(build(Model::SelectorExpression, current, *args)) end # For CaseExpression, setting the default for an already build CaseExpression def default r current.addOptions(Puppet::Pops::Model::Factory.WHEN(:default, r).current) self end def lambda=(lambda) current.lambda = lambda.current self end # Assignment = def set(r) f_build_binary_op(Model::AssignmentExpression, :'=', current, r) end # Assignment += def plus_set(r) f_build_binary_op(Model::AssignmentExpression, :'+=', current, r) end # Assignment -= def minus_set(r) f_build_binary_op(Model::AssignmentExpression, :'-=', current, r) end def attributes(*args) args.each {|a| current.addAttributes(build(a)) } self end # Catch all delegation to current def method_missing(meth, *args, &block) if current.respond_to?(meth) current.send(meth, *args, &block) else super end end def respond_to?(meth, include_all=false) current.respond_to?(meth, include_all) || super end def self.record_position(o, start_locatable, end_locateable) new(o).record_position(start_locatable, end_locateable) end # Records the position (start -> end) and computes the resulting length. # def record_position(start_locatable, end_locatable) from = start_locatable.is_a?(Puppet::Pops::Model::Factory) ? start_locatable.current : start_locatable to = end_locatable.is_a?(Puppet::Pops::Model::Factory) ? end_locatable.current : end_locatable to = from if to.nil? || to.offset.nil? o = current # record information directly in the Model::Positioned object o.offset = from.offset o.length ||= to.offset - from.offset + to.length self end # @return [Puppet::Pops::Adapters::SourcePosAdapter] with location information def loc() Puppet::Pops::Adapters::SourcePosAdapter.adapt(current) 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) expr = expr.current if expr.is_a?(Puppet::Pops::Model::Factory) # Note: Validation handles illegal combinations return false unless expr.is_a?(Puppet::Pops::Model::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) expr = expr.current if expr.is_a?(Puppet::Pops::Model::Factory) case expr when Model::QualifiedName :resource when Model::QualifiedReference :defaults when Model::AccessExpression # if Resource[e], then it is not resource specific if expr.left_expr.is_a?(Model::QualifiedReference) && expr.left_expr.value == 'resource' && expr.keys.size == 1 :defaults else :override end when 'class' :class else :error end end # Factory starting points def self.literal(o); new(o); end def self.minus(o); new(o).minus; end def self.unfold(o); new(o).unfold; end def self.var(o); new(o).var; end def self.block(*args); new(Model::BlockExpression, *args); end def self.string(*args); new(Model::ConcatenatedString, *args); end def self.text(o); new(o).text; end def self.IF(test_e,then_e,else_e); new(Model::IfExpression, test_e, then_e, else_e); end def self.UNLESS(test_e,then_e,else_e); new(Model::UnlessExpression, test_e, then_e, else_e); end def self.CASE(test_e,*options); new(Model::CaseExpression, test_e, *options); end def self.WHEN(values_list, block); new(Model::CaseOption, values_list, block); end def self.MAP(match, value); new(Model::SelectorEntry, match, value); end def self.TYPE(name, super_name=nil); new(Model::CreateTypeExpression, name, super_name); end def self.ATTR(name, type_expr=nil); new(Model::CreateAttributeExpression, name, type_expr); end def self.ENUM(*args); new(Model::CreateEnumExpression, *args); end def self.KEY_ENTRY(key, val); new(Model::KeyedEntry, key, val); end def self.HASH(entries); new(Model::LiteralHash, *entries); end def self.HEREDOC(name, expr); new(Model::HeredocExpression, name, expr); end def self.SUBLOCATE(token, expr) new(Model::SubLocatedExpression, token, expr); end def self.LIST(entries); new(Model::LiteralList, *entries); end def self.PARAM(name, expr=nil); new(Model::Parameter, name, expr); end def self.NODE(hosts, parent, body); new(Model::NodeDefinition, hosts, parent, body); end # Parameters # Mark parameter as capturing the rest of arguments def captures_rest() current.captures_rest = true end # Set Expression that should evaluate to the parameter's type def type_expr(o) current.type_expr = to_ops(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 = o.current if o.is_a?(Puppet::Pops::Model::Factory) o = new(Model::QualifiedName, o) unless o.is_a? Model::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 = o.current if o.is_a?(Puppet::Pops::Model::Factory) o = new(Model::QualifiedReference, o) unless o.is_a? Model::QualifiedReference o end def self.TEXT(expr) new(Model::TextExpression, new(expr).interpolate) end # TODO_EPP def self.RENDER_STRING(o) new(Model::RenderStringExpression, o) end def self.RENDER_EXPR(expr) new(Model::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(Model::EppExpression, parameters_specified, body)) end def self.RESERVED(name, future=false) new(Model::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(Model::QualifiedName, name) end def self.NUMBER(name_or_numeric) if n_radix = Puppet::Pops::Utils.to_n_with_radix(name_or_numeric) val, radix = n_radix if val.is_a?(Float) new(Model::LiteralFloat, val) else new(Model::LiteralInteger, val, radix) end else # Bad number should already have been caught by lexer - this should never happen raise ArgumentError, "Internal Error, NUMBER token does not contain a valid number, #{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 = Puppet::Pops::Utils.to_n_with_radix(name) val, radix = n_radix if val.is_a?(Float) new(Model::LiteralFloat, val) else new(Model::LiteralInteger, val, radix) end else new(Model::QualifiedName, name) end end def self.QREF(name) new(Model::QualifiedReference, name) end def self.VIRTUAL_QUERY(query_expr) new(Model::VirtualQuery, query_expr) end def self.EXPORTED_QUERY(query_expr) new(Model::ExportedQuery, query_expr) end def self.ATTRIBUTE_OP(name, op, expr) new(Model::AttributeOperation, name, op, expr) end def self.ATTRIBUTES_OP(expr) new(Model::AttributesOperation, expr) end def self.CALL_NAMED(name, rval_required, argument_list) unless name.kind_of?(Model::PopsObject) name = Puppet::Pops::Model::Factory.fqn(name) unless name.is_a?(Puppet::Pops::Model::Factory) end new(Model::CallNamedFunctionExpression, name, rval_required, *argument_list) end def self.CALL_METHOD(functor, argument_list) new(Model::CallMethodExpression, functor, true, nil, *argument_list) end def self.COLLECT(type_expr, query_expr, attribute_operations) new(Model::CollectExpression, type_expr, query_expr, attribute_operations) end def self.NAMED_ACCESS(type_name, bodies) new(Model::NamedAccessExpression, type_name, bodies) end def self.RESOURCE(type_name, bodies) new(Model::ResourceExpression, type_name, bodies) end def self.RESOURCE_DEFAULTS(type_name, attribute_operations) new(Model::ResourceDefaultsExpression, type_name, attribute_operations) end def self.RESOURCE_OVERRIDE(resource_ref, attribute_operations) new(Model::ResourceOverrideExpression, resource_ref, attribute_operations) end def self.RESOURCE_BODY(resource_title, attribute_operations) new(Model::ResourceBody, resource_title, attribute_operations) end def self.PROGRAM(body, definitions, locator) new(Model::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) if args.size > 1 new(Model::BlockExpression, *args) else new(args[0]) end end def self.HOSTCLASS(name, parameters, parent, body) new(Model::HostClassDefinition, name, parameters, parent, body) end def self.DEFINITION(name, parameters, body) new(Model::ResourceTypeDefinition, name, parameters, body) end def self.LAMBDA(parameters, body) new(Model::LambdaExpression, parameters, body) end def self.nop? o o.nil? || o.is_a?(Puppet::Pops::Model::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 } # Returns true if the given name is a "statement keyword" (require, include, contain, # error, notice, info, debug # def name_is_statement(name) STATEMENT_CALLS[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| expr = expr.current if expr.is_a?(Puppet::Pops::Model::Factory) name = memo[-1] if name.is_a?(Model::QualifiedName) && STATEMENT_CALLS[name.value] if expr.is_a?(Array) expr = expr.reject {|e| e.is_a?(Puppet::Pops::Parser::LexerSupport::TokenValue) } else expr = [expr] end the_call = Puppet::Pops::Model::Factory.CALL_NAMED(name, false, expr) # last positioned is last arg if there are several record_position(the_call, name, expr.is_a?(Array) ? expr[-1] : expr) memo[-1] = the_call if expr.is_a?(Model::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.is_a?(Model::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) # Returning nil means accepting the given as a potential resource expression return nil unless attribute_ops.is_a? Array return nil unless left.current.is_a?(Puppet::Pops::Model::QualifiedName) keyed_entries = attribute_ops.map do |ao| return nil if ao.operator == :'+>' KEY_ENTRY(ao.attribute_name, ao.value_expr) end result = block_or_expression(*transform_calls([left, HASH(keyed_entries)])) result 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 build_String(o) x = Model::LiteralString.new x.value = o; x end def build_NilClass(o) x = Model::Nop.new x end def build_TrueClass(o) x = Model::LiteralBoolean.new x.value = o x end def build_FalseClass(o) x = Model::LiteralBoolean.new x.value = o x end def build_Fixnum(o) x = Model::LiteralInteger.new x.value = o; x end def build_Float(o) x = Model::LiteralFloat.new x.value = o; x end def build_Regexp(o) x = Model::LiteralRegularExpression.new x.value = o; x end def build_EppExpression(o, parameters_specified, body) o.parameters_specified = parameters_specified b = f_build_body(body) o.body = b.current if b o end # If building a factory, simply unwrap the model oject contained in the factory. def build_Factory(o) o.current 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 build_Symbol(o) case o when :undef Model::LiteralUndef.new when :default Model::LiteralDefault.new else build_String(o.to_s) end end # Creates a LiteralList instruction from an Array, where the entries are built. def build_Array(o) x = Model::LiteralList.new o.each { |v| x.addValues(build(v)) } x 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 build_Hash(o) x = Model::LiteralHash.new (o.sort_by {|k,v| k.to_s}).each {|k,v| x.addEntries(build(Model::KeyedEntry.new, k, v)) } x end # @param rval_required [Boolean] if the call must produce a value def build_CallExpression(o, functor, rval_required, *args) o.functor_expr = to_ops(functor) o.rval_required = rval_required args.each {|x| o.addArguments(to_ops(x)) } o end def build_CallMethodExpression(o, functor, rval_required, lambda, *args) build_CallExpression(o, functor, rval_required, *args) o.lambda = lambda o end def build_CaseExpression(o, test, *args) o.test = build(test) args.each {|opt| o.addOptions(build(opt)) } o end def build_CaseOption(o, value_list, then_expr) value_list = [value_list] unless value_list.is_a? Array value_list.each { |v| o.addValues(build(v)) } b = f_build_body(then_expr) o.then_expr = to_ops(b) if b o end # Build a Class by creating an instance of it, and then calling build on the created instance # with the given arguments def build_Class(o, *args) build(o.new(), *args) end def interpolate_Factory(o) interpolate(o.current) end def interpolate_LiteralInteger(o) # convert number to a variable self.class.new(o).var end def interpolate_Object(o) o end def interpolate_QualifiedName(o) self.class.new(o).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(o) if is_interop_rewriteable?(o.left_expr) o.left_expr = to_ops(self.class.new(o.left_expr).interpolate) end o end def interpolate_NamedAccessExpression(o) if is_interop_rewriteable?(o.left_expr) o.left_expr = to_ops(self.class.new(o.left_expr).interpolate) end o end # Rewrite method calls on the form ${x.each ...} to ${$x.each} def interpolate_CallMethodExpression(o) if is_interop_rewriteable?(o.functor_expr) o.functor_expr = to_ops(self.class.new(o.functor_expr).interpolate) end o end def is_interop_rewriteable?(o) case o when Model::AccessExpression, Model::QualifiedName, Model::NamedAccessExpression, Model::CallMethodExpression true when Model::LiteralInteger # Only decimal integers can represent variables, else it is a number o.radix == 10 else false end end # Checks if the object is already a model object, or build it def to_ops(o, *args) case o when Model::PopsObject o when Puppet::Pops::Model::Factory o.current else build(o, *args) end end def self.concat(*args) new(args.map do |e| e = e.current if e.is_a?(self) case e when Model::LiteralString e.value when String e else raise ArgumentError, "can only concatenate strings, got #{e.class}" end end.join('')) end def to_s Puppet::Pops::Model::ModelTreeDumper.new.dump(self) end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/model/model.rb���������������������������������������������������������0000664�0052762�0001160�00000007053�12650174557�020550� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # The Puppet Pops Metamodel Implementation # # The Puppet Pops Metamodel consists of two parts; the metamodel expressed with RGen in model_meta.rb, # and this file which mixes in implementation details. # require 'rgen/metamodel_builder' require 'rgen/ecore/ecore' require 'rgen/ecore/ecore_ext' require 'rgen/ecore/ecore_to_ruby' module Puppet::Pops require 'puppet/pops/model/model_meta' # TODO: See PUP-2978 for possible performance optimization # Mix in implementation into the generated code module Model class PopsObject include Puppet::Pops::Visitable include Puppet::Pops::Adaptable include Puppet::Pops::Containment end class LocatableExpression module ClassModule # Go through the gymnastics of making either value or pattern settable # with synchronization to the other form. A derived value cannot be serialized # and we want to serialize the pattern. When recreating the object we need to # recreate it from the pattern string. # The below sets both values if one is changed. # def locator unless result = getLocator setLocator(result = Puppet::Pops::Parser::Locator.locator(source_text, source_ref(), line_offsets)) end result end end end class SubLocatedExpression module ClassModule def locator unless result = getLocator # Adapt myself to get the Locator for me adapter = Puppet::Pops::Adapters::SourcePosAdapter.adapt(self) # Get the program (root), and deal with case when not contained in a program program = eAllContainers.find {|c| c.is_a?(Program) } source_ref = program.nil? ? '' : program.source_ref # An outer locator is needed since SubLocator only deals with offsets. This outer locator # has 0,0 as origin. outer_locator = Puppet::Pops::Parser::Locator.locator(adpater.extract_text, source_ref, line_offsets) # Create a sublocator that describes an offset from the outer # NOTE: the offset of self is the same as the sublocator's leading_offset result = Puppet::Pops::Parser::Locator::SubLocator.new(outer_locator, leading_line_count, offset, leading_line_offset) setLocator(result) end result end end end class LiteralRegularExpression module ClassModule # Go through the gymnastics of making either value or pattern settable # with synchronization to the other form. A derived value cannot be serialized # and we want to serialize the pattern. When recreating the object we need to # recreate it from the pattern string. # The below sets both values if one is changed. # def value= regexp setValue regexp setPattern regexp.to_s end def pattern= regexp_string setPattern regexp_string setValue Regexp.new(regexp_string) end end end class AbstractResource module ClassModule def virtual_derived form == :virtual || form == :exported end def exported_derived form == :exported end end end class Program < PopsObject module ClassModule def locator unless result = getLocator setLocator(result = Puppet::Pops::Parser::Locator.locator(source_text, source_ref(), line_offsets)) end result end end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/model/model_label_provider.rb������������������������������������������0000664�0052762�0001160�00000015644�12650174557�023626� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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 Puppet::Pops::Model::ModelLabelProvider < Puppet::Pops::LabelProvider def initialize @@label_visitor ||= Puppet::Pops::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(o) end def label_Factory o ; label(o.current) 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_NodeDefinition o ; "Node 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_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 ; "#{Puppet::Pops::Types::TypeCalculator.string(o)}-Type" end def label_ReservedWord o ; "Reserved Word '#{o.word}'" end def label_PResourceType o if o.title "#{Puppet::Pops::Types::TypeCalculator.string(o)} Resource-Reference" else "#{Puppet::Pops::Types::TypeCalculator.string(o)}-Type" end end def label_Class o if o <= Puppet::Pops::Types::PAnyType simple_name = o.name.split('::').last simple_name[1..-5] + "-Type" else o.name end end end ��������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/model/model_meta.rb����������������������������������������������������0000664�0052762�0001160�00000046465�12650174557�021570� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # The Puppet Pops Metamodel # # This module contains a formal description of the Puppet Pops (*P*uppet *OP*eration instruction*S*). # It describes a Metamodel containing DSL instructions, a description of PuppetType and related # classes needed to evaluate puppet logic. # The metamodel resembles the existing AST model, but it is a semantic model of instructions and # the types that they operate on rather than an Abstract Syntax Tree, although closely related. # # The metamodel is anemic (has no behavior) except basic datatype and type # assertions and reference/containment assertions. # The metamodel is also a generalized description of the Puppet DSL to enable the # same metamodel to be used to express Puppet DSL models (instances) with different semantics as # the language evolves. # # The metamodel is concretized by a validator for a particular version of # the Puppet DSL language. # # This metamodel is expressed using RGen. # require 'rgen/metamodel_builder' module Puppet::Pops::Model extend RGen::MetamodelBuilder::ModuleExtension # A base class for modeled objects that makes them Visitable, and Adaptable. # class PopsObject < RGen::MetamodelBuilder::MMBase abstract end # A Positioned object has an offset measured in an opaque unit (representing characters) from the start # of a source text (starting # from 0), and a length measured in the same opaque unit. The resolution of the opaque unit requires the # aid of a Locator instance that knows about the measure. This information is stored in the model's # root node - a Program. # # The offset and length are optional if the source of the model is not from parsed text. # class Positioned < PopsObject abstract has_attr 'offset', Integer has_attr 'length', Integer end # @abstract base class for expressions class Expression < Positioned abstract end # A Nop - the "no op" expression. # @note not really needed since the evaluator can evaluate nil with the meaning of NoOp # @todo deprecate? May be useful if there is the need to differentiate between nil and Nop when transforming model. # class Nop < Expression end # A binary expression is abstract and has a left and a right expression. The order of evaluation # and semantics are determined by the concrete subclass. # class BinaryExpression < Expression abstract # # @!attribute [rw] left_expr # @return [Expression] contains_one_uni 'left_expr', Expression, :lowerBound => 1 contains_one_uni 'right_expr', Expression, :lowerBound => 1 end # An unary expression is abstract and contains one expression. The semantics are determined by # a concrete subclass. # class UnaryExpression < Expression abstract contains_one_uni 'expr', Expression, :lowerBound => 1 end # A class that simply evaluates to the contained expression. # It is of value in order to preserve user entered parentheses in transformations, and # transformations from model to source. # class ParenthesizedExpression < UnaryExpression; end # A boolean not expression, reversing the truth of the unary expr. # class NotExpression < UnaryExpression; end # An arithmetic expression reversing the polarity of the numeric unary expr. # class UnaryMinusExpression < UnaryExpression; end # Unfolds an array (a.k.a 'splat') class UnfoldExpression < UnaryExpression; end OpAssignment = RGen::MetamodelBuilder::DataTypes::Enum.new( :literals => [:'=', :'+=', :'-='], :name => 'OpAssignment') # An assignment expression assigns a value to the lval() of the left_expr. # class AssignmentExpression < BinaryExpression has_attr 'operator', OpAssignment, :lowerBound => 1 end OpArithmetic = RGen::MetamodelBuilder::DataTypes::Enum.new( :literals => [:'+', :'-', :'*', :'%', :'/', :'<<', :'>>' ], :name => 'OpArithmetic') # An arithmetic expression applies an arithmetic operator on left and right expressions. # class ArithmeticExpression < BinaryExpression has_attr 'operator', OpArithmetic, :lowerBound => 1 end OpRelationship = RGen::MetamodelBuilder::DataTypes::Enum.new( :literals => [:'->', :'<-', :'~>', :'<~'], :name => 'OpRelationship') # A relationship expression associates the left and right expressions # class RelationshipExpression < BinaryExpression has_attr 'operator', OpRelationship, :lowerBound => 1 end # A binary expression, that accesses the value denoted by right in left. i.e. typically # expressed concretely in a language as left[right]. # class AccessExpression < Expression contains_one_uni 'left_expr', Expression, :lowerBound => 1 contains_many_uni 'keys', Expression, :lowerBound => 1 end OpComparison = RGen::MetamodelBuilder::DataTypes::Enum.new( :literals => [:'==', :'!=', :'<', :'>', :'<=', :'>=' ], :name => 'OpComparison') # A comparison expression compares left and right using a comparison operator. # class ComparisonExpression < BinaryExpression has_attr 'operator', OpComparison, :lowerBound => 1 end OpMatch = RGen::MetamodelBuilder::DataTypes::Enum.new( :literals => [:'!~', :'=~'], :name => 'OpMatch') # A match expression matches left and right using a matching operator. # class MatchExpression < BinaryExpression has_attr 'operator', OpMatch, :lowerBound => 1 end # An 'in' expression checks if left is 'in' right # class InExpression < BinaryExpression; end # A boolean expression applies a logical connective operator (and, or) to left and right expressions. # class BooleanExpression < BinaryExpression abstract end # An and expression applies the logical connective operator and to left and right expression # and does not evaluate the right expression if the left expression is false. # class AndExpression < BooleanExpression; end # An or expression applies the logical connective operator or to the left and right expression # and does not evaluate the right expression if the left expression is true # class OrExpression < BooleanExpression; end # A literal list / array containing 0:M expressions. # class LiteralList < Expression contains_many_uni 'values', Expression end # A Keyed entry has a key and a value expression. It is typically used as an entry in a Hash. # class KeyedEntry < Positioned contains_one_uni 'key', Expression, :lowerBound => 1 contains_one_uni 'value', Expression, :lowerBound => 1 end # A literal hash is a collection of KeyedEntry objects # class LiteralHash < Expression contains_many_uni 'entries', KeyedEntry end # A block contains a list of expressions # class BlockExpression < Expression contains_many_uni 'statements', Expression end # A case option entry in a CaseStatement # class CaseOption < Expression contains_many_uni 'values', Expression, :lowerBound => 1 contains_one_uni 'then_expr', Expression, :lowerBound => 1 end # A case expression has a test, a list of options (multi values => block map). # One CaseOption may contain a LiteralDefault as value. This option will be picked if nothing # else matched. # class CaseExpression < Expression contains_one_uni 'test', Expression, :lowerBound => 1 contains_many_uni 'options', CaseOption end # A query expression is an expression that is applied to some collection. # The contained optional expression may contain different types of relational expressions depending # on what the query is applied to. # class QueryExpression < Expression abstract contains_one_uni 'expr', Expression, :lowerBound => 0 end # An exported query is a special form of query that searches for exported objects. # class ExportedQuery < QueryExpression end # A virtual query is a special form of query that searches for virtual objects. # class VirtualQuery < QueryExpression end OpAttribute = RGen::MetamodelBuilder::DataTypes::Enum.new( :literals => [:'=>', :'+>', ], :name => 'OpAttribute') class AbstractAttributeOperation < Positioned end # An attribute operation sets or appends a value to a named attribute. # class AttributeOperation < AbstractAttributeOperation has_attr 'attribute_name', String, :lowerBound => 1 has_attr 'operator', OpAttribute, :lowerBound => 1 contains_one_uni 'value_expr', Expression, :lowerBound => 1 end # An attribute operation containing an expression that must evaluate to a Hash # class AttributesOperation < AbstractAttributeOperation contains_one_uni 'expr', Expression, :lowerBound => 1 end # An object that collects stored objects from the central cache and returns # them to the current host. Operations may optionally be applied. # class CollectExpression < Expression contains_one_uni 'type_expr', Expression, :lowerBound => 1 contains_one_uni 'query', QueryExpression, :lowerBound => 1 contains_many_uni 'operations', AbstractAttributeOperation end class Parameter < Positioned has_attr 'name', String, :lowerBound => 1 contains_one_uni 'value', Expression contains_one_uni 'type_expr', Expression, :lowerBound => 0 has_attr 'captures_rest', Boolean end # Abstract base class for definitions. # class Definition < Expression abstract end # Abstract base class for named and parameterized definitions. class NamedDefinition < Definition abstract has_attr 'name', String, :lowerBound => 1 contains_many_uni 'parameters', Parameter contains_one_uni 'body', Expression end # A resource type definition (a 'define' in the DSL). # class ResourceTypeDefinition < NamedDefinition end # A node definition matches hosts using Strings, or Regular expressions. It may inherit from # a parent node (also using a String or Regular expression). # class NodeDefinition < Definition contains_one_uni 'parent', Expression contains_many_uni 'host_matches', Expression, :lowerBound => 1 contains_one_uni 'body', Expression end class LocatableExpression < Expression has_many_attr 'line_offsets', Integer has_attr 'locator', Object, :lowerBound => 1, :transient => true end # Contains one expression which has offsets reported virtually (offset against the Program's # overall locator). # class SubLocatedExpression < Expression contains_one_uni 'expr', Expression, :lowerBound => 1 # line offset index for contained expressions has_many_attr 'line_offsets', Integer # Number of preceding lines (before the line_offsets) has_attr 'leading_line_count', Integer # The offset of the leading source line (i.e. size of "left margin"). has_attr 'leading_line_offset', Integer # The locator for the sub-locatable's children (not for the sublocator itself) # The locator is not serialized and is recreated on demand from the indexing information # in self. # has_attr 'locator', Object, :lowerBound => 1, :transient => true end # A heredoc is a wrapper around a LiteralString or a ConcatenatedStringExpression with a specification # of syntax. The expectation is that "syntax" has meaning to a validator. A syntax of nil or '' means # "unspecified syntax". # class HeredocExpression < Expression has_attr 'syntax', String contains_one_uni 'text_expr', Expression, :lowerBound => 1 end # A class definition # class HostClassDefinition < NamedDefinition has_attr 'parent_class', String end # i.e {|parameters| body } class LambdaExpression < Expression contains_many_uni 'parameters', Parameter contains_one_uni 'body', Expression end # If expression. If test is true, the then_expr part should be evaluated, else the (optional) # else_expr. An 'elsif' is simply an else_expr = IfExpression, and 'else' is simply else == Block. # a 'then' is typically a Block. # class IfExpression < Expression contains_one_uni 'test', Expression, :lowerBound => 1 contains_one_uni 'then_expr', Expression, :lowerBound => 1 contains_one_uni 'else_expr', Expression end # An if expression with boolean reversed test. # class UnlessExpression < IfExpression end # An abstract call. # class CallExpression < Expression abstract # A bit of a crutch; functions are either procedures (void return) or has an rvalue # this flag tells the evaluator that it is a failure to call a function that is void/procedure # where a value is expected. # has_attr 'rval_required', Boolean, :defaultValueLiteral => "false" contains_one_uni 'functor_expr', Expression, :lowerBound => 1 contains_many_uni 'arguments', Expression contains_one_uni 'lambda', Expression end # A function call where the functor_expr should evaluate to something callable. # class CallFunctionExpression < CallExpression; end # A function call where the given functor_expr should evaluate to the name # of a function. # class CallNamedFunctionExpression < CallExpression; end # A method/function call where the function expr is a NamedAccess and with support for # an optional lambda block # class CallMethodExpression < CallExpression end # Abstract base class for literals. # class Literal < Expression abstract end # A literal value is an abstract value holder. The type of the contained value is # determined by the concrete subclass. # class LiteralValue < Literal abstract end # A Regular Expression Literal. # class LiteralRegularExpression < LiteralValue has_attr 'value', Object, :lowerBound => 1, :transient => true has_attr 'pattern', String, :lowerBound => 1 end # A Literal String # class LiteralString < LiteralValue has_attr 'value', String, :lowerBound => 1 end class LiteralNumber < LiteralValue abstract end # A literal number has a radix of decimal (10), octal (8), or hex (16) to enable string conversion with the input radix. # By default, a radix of 10 is used. # class LiteralInteger < LiteralNumber has_attr 'radix', Integer, :lowerBound => 1, :defaultValueLiteral => "10" has_attr 'value', Integer, :lowerBound => 1 end class LiteralFloat < LiteralNumber has_attr 'value', Float, :lowerBound => 1 end # The DSL `undef`. # class LiteralUndef < Literal; end # The DSL `default` class LiteralDefault < Literal; end # DSL `true` or `false` class LiteralBoolean < LiteralValue has_attr 'value', Boolean, :lowerBound => 1 end # A text expression is an interpolation of an expression. If the embedded expression is # a QualifiedName, it is taken as a variable name and resolved. All other expressions are evaluated. # The result is transformed to a string. # class TextExpression < UnaryExpression; end # An interpolated/concatenated string. The contained segments are expressions. Verbatim sections # should be LiteralString instances, and interpolated expressions should either be # TextExpression instances (if QualifiedNames should be turned into variables), or any other expression # if such treatment is not needed. # class ConcatenatedString < Expression contains_many_uni 'segments', Expression end # A DSL NAME (one or multiple parts separated by '::'). # class QualifiedName < LiteralValue has_attr 'value', String, :lowerBound => 1 end # Represents a parsed reserved word. If the 'future' attribute is true # the ReserwedWord functions as a deprecation warning + string value # class ReservedWord < LiteralValue has_attr 'word', String, :lowerBound => 1 has_attr 'future', Boolean end # A DSL CLASSREF (one or multiple parts separated by '::' where (at least) the first part starts with an upper case letter). # class QualifiedReference < LiteralValue has_attr 'value', String, :lowerBound => 1 end # A Variable expression looks up value of expr (some kind of name) in scope. # The expression is typically a QualifiedName, or QualifiedReference. # class VariableExpression < UnaryExpression; end # Epp start class EppExpression < Expression # EPP can be specified without giving any parameter specification. # However, the parameters of the lambda in that case are the empty # array, which is the same as when the parameters are explicity # specified as empty. This attribute tracks that difference. has_attr 'parameters_specified', Boolean contains_one_uni 'body', Expression end # A string to render class RenderStringExpression < LiteralString end # An expression to evluate and render class RenderExpression < UnaryExpression end # A resource body describes one resource instance # class ResourceBody < Positioned contains_one_uni 'title', Expression contains_many_uni 'operations', AbstractAttributeOperation end ResourceFormEnum = RGen::MetamodelBuilder::DataTypes::Enum.new( :literals => [:regular, :virtual, :exported ], :name => 'ResourceFormEnum') # An abstract resource describes the form of the resource (regular, virtual or exported) # and adds convenience methods to ask if it is virtual or exported. # All derived classes may not support all forms, and these needs to be validated # class AbstractResource < Expression abstract has_attr 'form', ResourceFormEnum, :lowerBound => 1, :defaultValueLiteral => "regular" has_attr 'virtual', Boolean, :derived => true has_attr 'exported', Boolean, :derived => true end # A resource expression is used to instantiate one or many resource. Resources may optionally # be virtual or exported, an exported resource is always virtual. # class ResourceExpression < AbstractResource contains_one_uni 'type_name', Expression, :lowerBound => 1 contains_many_uni 'bodies', ResourceBody end # A resource defaults sets defaults for a resource type. This class inherits from AbstractResource # but does only support the :regular form (this is intentional to be able to produce better error messages # when illegal forms are applied to a model. # class ResourceDefaultsExpression < AbstractResource contains_one_uni 'type_ref', Expression contains_many_uni 'operations', AbstractAttributeOperation end # A resource override overrides already set values. # class ResourceOverrideExpression < AbstractResource contains_one_uni 'resources', Expression, :lowerBound => 1 contains_many_uni 'operations', AbstractAttributeOperation end # A selector entry describes a map from matching_expr to value_expr. # class SelectorEntry < Positioned contains_one_uni 'matching_expr', Expression, :lowerBound => 1 contains_one_uni 'value_expr', Expression, :lowerBound => 1 end # A selector expression represents a mapping from a left_expr to a matching SelectorEntry. # class SelectorExpression < Expression contains_one_uni 'left_expr', Expression, :lowerBound => 1 contains_many_uni 'selectors', SelectorEntry end # A named access expression looks up a named part. (e.g. $a.b) # class NamedAccessExpression < BinaryExpression; end # A Program is the top level construct returned by the parser # it contains the parsed result in the body, and has a reference to the full source text, # and its origin. The line_offset's is an array with the start offset of each line. # class Program < PopsObject contains_one_uni 'body', Expression has_many 'definitions', Definition has_attr 'source_text', String has_attr 'source_ref', String has_many_attr 'line_offsets', Integer has_attr 'locator', Object, :lowerBound => 1, :transient => true end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/model/model_tree_dumper.rb���������������������������������������������0000664�0052762�0001160�00000022402�12650174557�023136� 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_Factory o do_dump(o.current) 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 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 "/#{o.value.source}/" 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) result = ["@(#{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_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_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_ResourceTypeDefinition o result = dump_NamedDefinition(o) result[0] = 'define' result end def dump_ResourceOverrideExpression o form = o.form == :regular ? '' : o.form.to_s + "-" 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 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.to_s + "-" 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.to_s + "-" 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_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-3.8.5/lib/puppet/pops/model/tree_dumper.rb���������������������������������������������������0000664�0052762�0001160�00000002356�12650174557�021764� 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(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 whitepsace 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-3.8.5/lib/puppet/pops/parser/����������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017311� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/parser/code_merger.rb��������������������������������������������������0000664�0052762�0001160�00000001320�12650174557�022106� 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. # children = parse_results.select {|x| !x.nil? && x.code}.reduce([]) do |memo, parsed_class| memo << parsed_class.code end Puppet::Parser::AST::BlockExpression.new(:children => children) end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/parser/egrammar.ra�����������������������������������������������������0000664�0052762�0001160�00000065252�12650174557�021443� 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 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 PRIVATE ATTR TYPE token APPLICATION_R CONSUMES_R PRODUCES_R token LOW prechigh left HIGH left SEMIC left PIPE left LPAREN left RPAREN left DOT nonassoc EPP_START left LBRACK LISTSTART left RBRACK left QMARK 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 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(Factory.block_or_expression(*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] } assignments : assignment { result = [val[0]] } | assignments COMMA assignment { result = val[0].push(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]) 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 | call_function_expression | expression LBRACK expressions RBRACK =LBRACK { result = val[0][*val[2]] ; loc result, val[0], val[3] } | 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] == 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] } #---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 | hostclass_expression | node_definition_expression | epp_render_expression | 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 : expression LPAREN assignments endcomma RPAREN { result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] } | expression LPAREN RPAREN { result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] } | expression LPAREN assignments endcomma RPAREN lambda { result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] result.lambda = val[5] } | expression LPAREN RPAREN lambda { result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] result.lambda = val[3] } #---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 assignments 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] } #---LAMBDA # lambda : lambda_parameter_list lambda_rest { result = Factory.LAMBDA(val[0][:value], val[1][:value]) loc result, val[0][:start], val[1][: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[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]) loc result, val[0], 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[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]) loc result, val[0], 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 | APPLICATION_R | CONSUMES_R | PRODUCES_R # | BOOLEAN # 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 { result = add_definition(Factory.DEFINITION(classname(val[1][:value]), val[2], val[4])) loc result, val[0], val[5] # New lexer does not keep track of this, this is done in validation if @lexer.respond_to?(:'indefine=') @lexer.indefine = false end } #---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 result = add_definition(Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), val[5])) loc result, val[0], val[6] } # 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 #---NODE # # Produces Model::NodeDefinition # node_definition_expression : NODE hostnames endcomma nodeparent LBRACE statements RBRACE { result = add_definition(Factory.NODE(val[1], val[3], val[5])) loc result, val[0], val[6] } | NODE hostnames endcomma nodeparent LBRACE RBRACE { result = add_definition(Factory.NODE(val[1], val[3], nil)) loc result, val[0], val[5] } # 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 # For now the function word will just be reserved, in the future it will # produce a function definition # FUNCTION classname parameter_list LBRACE opt_statements RBRACE { # result = add_definition(Factory.FUNCTION(val[1][:value], val[2], val[4])) # loc result, val[0], val[5] # } #---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 | APPLICATION_R | CONSUMES_R | PRODUCES_R | CLASS { error val[0], "'class' is not a valid classname" } # Produces Array<Model::Parameter> parameter_list : nil { result = [] } | LPAREN RPAREN { result = [] } | LPAREN 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 expressions RBRACK { result = val[0][*val[2]] ; loc result, val[0], val[3] } #--VARIABLE # variable : VARIABLE { result = Factory.fqn(val[0][:value]).var ; loc result, val[0] } #---RESERVED WORDS # reserved_word : FUNCTION { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] } | PRIVATE { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] } | TYPE { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] } | ATTR { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] } | APPLICATION_R { result = Factory.RESERVED(val[0][:value], true) ; loc result, val[0] } | CONSUMES_R { result = Factory.RESERVED(val[0][:value], true) ; loc result, val[0] } | PRODUCES_R { result = Factory.RESERVED(val[0][:value], true) ; loc result, val[0] } #---LITERALS (dynamic and static) # array : LISTSTART assignments 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 assignments 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 : assignment FARROW assignment { result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] } 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])); 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 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-3.8.5/lib/puppet/pops/parser/eparser.rb������������������������������������������������������0000664�0052762�0001160�00000271151�12650174557�021307� 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', 765) # Make emacs happy # Local Variables: # mode: ruby # End: ...end egrammar.ra/module_eval... ##### State transition tables begin ### clist = [ '61,64,331,-248,62,56,289,57,-247,83,-250,-249,247,141,286,387,332,406', '383,-133,382,-132,289,104,18,107,288,102,103,161,42,289,45,248,47,12', '-131,46,36,39,347,44,37,10,11,-248,142,69,17,106,-247,38,-250,-249,15', '16,287,87,88,90,89,-133,70,-132,85,86,-236,-245,43,278,279,84,35,65', '-129,67,68,66,-131,348,48,49,51,50,52,53,54,61,64,55,263,62,56,264,57', '91,83,61,64,260,383,62,382,267,266,313,284,326,-236,-245,104,18,107', '265,102,103,-129,42,247,45,354,47,121,82,46,36,39,247,44,37,356,69,82', '82,69,17,106,82,38,248,69,15,16,74,87,88,90,89,248,70,343,85,86,240', '340,43,364,365,84,35,65,284,67,68,285,367,265,48,49,51,50,52,53,54,61', '64,55,314,62,56,370,57,91,83,260,326,375,376,316,260,259,258,380,320', '384,386,330,104,18,107,114,102,103,236,42,239,45,393,47,12,237,46,36', '39,72,44,37,10,11,395,247,69,17,106,284,38,236,82,15,16,82,119,226,90', '89,113,70,225,85,86,114,399,43,115,74,84,35,65,127,67,68,402,284,127', '48,49,51,50,52,53,54,61,64,55,405,62,56,158,57,91,83,119,327,326,156', '113,409,386,411,412,116,117,118,115,104,18,107,413,102,103,128,42,416', '45,417,47,12,321,46,36,39,418,44,37,10,11,247,127,69,17,106,82,38,74', '426,15,16,71,428,69,116,117,118,70,429,430,114,248,316,43,,,114,35,65', '110,67,68,247,,,48,49,51,50,52,53,54,61,64,55,69,62,56,,57,334,119,', '248,,113,,119,,,114,113,,115,,,18,276,,115,,,42,,45,,47,12,,46,36,39', ',44,37,10,11,,,69,17,119,,38,,113,15,16,373,,116,117,118,115,70,,116', '117,118,,43,,114,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62', '56,,57,422,83,,116,117,118,119,,,,113,,,,,104,18,107,115,102,103,,42', ',45,,47,12,,46,36,39,,44,37,10,11,,,69,17,106,,38,,,15,16,75,76,77,', ',,70,116,117,118,,,43,,,84,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64', '55,,62,56,,57,336,83,,,,,,,,,78,80,79,81,,104,18,107,,102,103,,42,,45', ',47,12,,46,36,39,,44,37,10,11,,,69,17,106,,38,,,15,16,,,,90,89,,70,', '85,86,,,43,,,84,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56', ',57,91,83,,,,,,,,,,,,,,104,18,107,,102,103,,42,,45,,47,12,,46,36,39', ',44,37,10,11,,,69,17,106,,38,,,15,16,,,,,,,70,,85,86,,,43,,,84,35,65', ',67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,83,,,,,,,,,,,,,', '104,18,107,,102,103,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,69,17,106', ',38,,,15,16,,,,,,,70,,85,86,,,43,,,84,35,65,,67,68,,,,48,49,51,50,52', '53,54,61,64,55,83,62,56,,57,,83,,,,,,,,104,,107,,102,103,104,18,107', ',102,103,,42,,45,,47,121,,46,36,39,,44,37,106,,,,69,17,106,,38,,,15', '16,,,85,86,,,70,,,84,,,43,,,84,35,65,,67,68,,,,48,49,51,50,52,53,54', '61,64,55,83,62,56,,57,,83,,,,,,,,104,,107,,102,103,104,18,107,,102,103', ',42,,45,,47,121,,46,36,39,,44,37,106,,,,69,17,106,,38,,,15,16,,,,,,', '70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,83,62,56', ',57,,,,,,,,,,104,,107,,102,103,,18,,,,,,42,,45,,47,121,,46,36,39,,44', '37,106,,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48', '49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45', ',47,121,,46,36,39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,', '35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,', ',,,,,18,,,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,69,17,,,38,,,15', '16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55', ',62,56,,57,420,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,12,,46,36,39,,44,37', '10,11,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49', '51,50,52,53,54,61,64,55,,62,56,,57,415,,,,,,,,,,,,,,,,18,,,,,,42,,45', ',47,12,,46,36,39,,44,37,10,11,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43', ',,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,', ',,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39,,44,37,,,,,69,17,,,38,,,15', '16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55', ',62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39,,44,37,', ',,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50', '52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121', ',46,36,39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67', '68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,', ',,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,69,17,,,38,,,15,16,,,,,,,70', ',,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57', '349,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,69', '17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53', '54,61,64,55,,62,56,145,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,12,,46', '36,39,,44,37,10,11,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67', '68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,147,57,,,,,,,,,,,,,,,,,18', ',,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,69,17,,,38,,,15,16,,,,,', ',70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56', ',57,149,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,12,,46,36,39,,44,37,10,11', ',,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50', '52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,12,', '46,36,39,,44,37,10,11,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65', ',67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18', ',,,,,42,,45,,47,121,,46,36,39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70', ',,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57', ',,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39,,44,37,,,,,69,17,,', '38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54', '61,64,55,,62,56,,160,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39', ',44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48', '49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45', ',47,12,,46,36,39,,44,37,10,11,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43', ',,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,', ',,,,,,,,18,,,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,69,17,,,38,,', '15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64', '55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39,,44,37', ',,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51', '50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,12', ',46,36,39,,44,37,10,11,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65', ',67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18', ',,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,69,17,,,38,,,15,16,,,,,', ',70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56', ',57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,69', '17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53', '54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,12,,46,36', '39,,44,37,10,11,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68', ',,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,', '42,,45,,47,12,,46,36,39,,44,37,10,11,,,69,17,,,38,,,15,16,,,,,,,70,', ',,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,', ',,,,,,,,,,,,,,,18,,,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,69,17', ',,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54', '61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39', ',44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48', '49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45', ',47,12,,46,36,39,,44,37,10,11,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43', ',,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,', ',,,,,,,,18,,,,,,177,194,186,195,47,187,197,188,36,176,,179,174,,,,,69', '17,198,193,175,,,15,173,,,,,,,70,,,,,196,178,,,,35,65,,67,68,,,,189', '190,192,191,182,183,184,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,', '42,,45,,47,121,,46,36,39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,,,,', ',43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,', ',,,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39,,44,37,,,,,69,17,,,38,', ',15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64', '55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39,,44,37', ',,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51', '50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121', ',46,36,39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67', '68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,', ',,,42,,45,,47,121,,46,36,39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,', ',,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,', ',,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39,,44,37,,,,,69,17,,,38', ',,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61', '64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39,,44', '37,,,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49', '51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47', '121,,46,36,39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65', ',67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18', ',,,,,42,,45,,47,121,,46,36,39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70', ',,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57', ',,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39,,44,37,,,,,69,17,,', '38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54', '61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39', ',44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48', '49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45', ',47,121,,46,36,39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,', '35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,', ',,,,,18,,,,,,42,,45,,47,121,,46,36,39,,44,37,,,,,69,17,,,38,,,15,16', ',,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62', '56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39,,44,37,,,,,69', '17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53', '54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36', '39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,', ',48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42', ',45,,47,121,,46,36,39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43', ',,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,', ',,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39,,44,37,,,,,69,17,,,38,,,15', '16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55', ',62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39,,44,37,', ',,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50', '52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121', ',46,36,39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67', '68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,', ',,,42,,45,,47,121,,46,36,39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,', ',,,,43,,,222,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57', ',,,,,,,,,,,,,,,,18,224,,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,69', '17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53', '54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,12,,46,36', '39,,44,37,10,11,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68', ',,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,', '42,,45,,47,121,,46,36,39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,,,,', ',43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,', ',,,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39,,44,37,,,,,69,17,,,38,', ',15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64', '55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39,,44,37', ',,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51', '50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121', ',46,36,39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67', '68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,', ',,,42,,45,,47,121,,46,36,39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,', ',,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,', ',,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39,,44,37,,,,,69,17,,,38', ',,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61', '64,55,,62,56,,57,,,285,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39', ',44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48', '49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45', ',47,12,,46,36,39,,44,37,10,11,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43', ',,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,', ',,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39,,44,37,,,,,69,17,,,38,,,15', '16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55', ',62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39,,44,37,', ',,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50', '52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121', ',46,36,39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67', '68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,149,,,,,,,,,,,,,,,,18', ',,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,69,17,,,38,,,15,16,,,,,', ',70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56', ',57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,69', '17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53', '54,61,64,55,,62,56,,57,324,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,12,,46', '36,39,,44,37,10,11,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67', '68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,252', ',,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,69,17,,,38,,,15,16,,,,,,', '70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,', '57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,69', '17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53', '54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36', '39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,', ',48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42', ',45,,47,121,,46,36,39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43', ',,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,388,,,,', ',,,,,,,,,,,18,,,,,,42,,45,,47,121,,46,36,39,,44,37,,,,,69,17,,,38,,', '15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64', '55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,12,,46,36,39,,44,37', '10,11,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65,,67,68,,,,48,49', '51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47', '121,,46,36,39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70,,,,,,43,,,,35,65', ',67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57,,,,,,,,,,,,,,,,,18', ',,,,,42,,45,,47,121,,46,36,39,,44,37,,,,,69,17,,,38,,,15,16,,,,,,,70', ',,,,,43,,,,35,65,,67,68,,,,48,49,51,50,52,53,54,61,64,55,,62,56,,57', ',,,,,,,,,,,,,,,,18,61,64,,,62,42,,45,,47,12,,46,36,39,,44,37,10,11,', ',69,17,,,38,,,15,16,,,,,,,70,,141,,,138,43,,,,35,65,83,67,68,,,,48,49', '51,50,52,53,54,70,104,55,107,83,102,103,,,,142,65,,,,,,,104,,107,,102', '103,,,,106,,,,,,,,87,88,90,89,92,93,,85,86,106,,,,,84,,,87,88,90,89', '92,93,,85,86,,83,,255,,84,,,,,,,91,99,94,104,,107,,102,103,,95,97,96', '98,,61,64,91,,62,,,,,,,,,,106,,,,101,100,,,87,88,90,89,92,93,,85,86', '83,,254,,,84,,,141,,,138,99,94,104,,107,,102,103,,95,97,96,98,,61,64', ',91,62,70,,,,,,,,,106,142,65,,101,100,,,87,88,90,89,92,93,,85,86,83', ',253,,,84,,,141,,,138,99,94,104,,107,,102,103,,95,97,96,98,,,,,91,,70', ',,,,,,,,106,142,65,,101,100,,,87,88,90,89,92,93,83,85,86,,,,,,84,,,', '99,94,104,317,107,,102,103,,95,97,96,98,,,,,,,,91,,,,,,,,106,,,,101', '100,,83,87,88,90,89,92,93,,85,86,,,99,94,104,84,107,,102,103,,95,97', '96,98,,,,,,,,,,,,,,,91,106,,,,101,100,,83,87,88,90,89,92,93,,85,86,', ',99,94,104,84,107,83,102,103,,95,97,96,98,,278,279,,,94,104,,107,,102', '103,,95,91,106,,,,101,100,,,87,88,90,89,92,93,,85,86,106,,,,,84,,,87', '88,90,89,92,93,,85,86,,83,,,,84,,,,,,,91,99,94,104,282,107,,102,103', ',95,97,96,98,,,,91,,,,,,,,,,,,106,,,,101,100,,83,87,88,90,89,92,93,', '85,86,,,99,94,104,84,107,,102,103,,95,97,96,98,,,,,,,,,,,,,,,91,106', ',,,101,100,,83,87,88,90,89,92,93,,85,86,,,99,94,104,84,107,,102,103', ',95,97,96,98,,,,,,,,,,,,,,,91,106,,,,101,100,,83,87,88,90,89,92,93,', '85,86,,,99,94,104,84,107,,102,103,,95,97,96,98,,,,,,,,,,,,,,,91,106', ',,,101,100,,83,87,88,90,89,92,93,,85,86,,,99,94,104,84,107,83,102,103', ',95,97,96,98,,,,,,94,104,,107,,102,103,,95,91,106,,,,101,100,,,87,88', '90,89,92,93,,85,86,106,,,,,84,,,87,88,90,89,92,93,83,85,86,,,,,,84,', ',,99,94,104,91,107,,102,103,,95,97,96,98,,,,,,,,91,,,,,,,,106,,,,101', '100,,83,87,88,90,89,92,93,,85,86,,,99,94,104,84,107,,102,103,,95,97', '96,98,,,,,,,,,,,,,,,91,106,,,,101,100,,83,87,88,90,89,92,93,,85,86,', ',99,94,104,84,107,83,102,103,,95,97,96,98,,,,,,94,104,,107,,102,103', ',95,91,106,,,,101,100,,,87,88,90,89,92,93,,85,86,106,,,,,84,,,87,88', '90,89,92,93,83,85,86,,,,,,84,,,,99,94,104,91,107,,102,103,,95,97,96', '98,,,,,,,,91,,,,,,,,106,,,,,,,,87,88,90,89,92,93,83,85,86,,,290,,,84', ',,,99,94,104,,107,,102,103,,95,97,96,98,,,,,,,,91,,,,,,,,106,,,,101', '100,,83,87,88,90,89,92,93,,85,86,,,99,94,104,84,107,83,102,103,,95,97', '96,98,,,,,,94,104,,107,,102,103,,95,91,106,,,,,100,,,87,88,90,89,92', '93,,85,86,106,,,,,84,,,87,88,90,89,92,93,83,85,86,,,,,,84,,,,99,94,104', '91,107,,102,103,,95,97,96,98,61,64,,,62,,,91,,,,,,,,106,,,,101,100,', ',87,88,90,89,92,93,,85,86,,,,,,84,,141,,,138,,305,194,304,195,,302,197', '306,,296,,301,303,,,,,91,70,198,193,307,,,,300,,,142,65,,,,,,,,196,308', ',,,,,,,,,,,311,312,310,309,297,298,299,305,194,304,195,,302,197,306', ',296,,301,303,,,,,,,198,193,307,,,,300,,,,,,,,,,,,196,308,,,,,,,,,,', ',311,312,310,309,297,298,299,305,194,304,195,,302,197,306,,296,,301', '303,,,,,,,198,193,307,,,,300,,,,,,,,,,,,196,308,,,,,,,,,,,,311,312,310', '309,297,298,299,305,194,304,195,,302,197,306,,296,,301,303,,,,,,,198', '193,307,,,,300,,,,,,,,,,,,196,308,,,,,,,,,,,,311,312,310,309,297,298', '299' ] racc_action_table = arr = ::Array.new(8687, 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,251,192,0,0,337,0,191,210,190,189,249,259,185,337,251,379,380,184', '380,183,379,210,0,210,200,210,210,71,0,200,0,249,0,0,182,0,0,0,261,0', '0,0,0,192,259,0,0,210,191,0,190,189,0,0,185,210,210,210,210,184,0,183', '210,210,174,175,0,354,354,210,0,0,176,0,0,0,182,262,0,0,0,0,0,0,0,417', '417,0,148,417,417,148,417,210,209,156,156,223,334,156,334,153,153,228', '172,275,174,175,209,417,209,280,209,209,176,417,127,417,281,417,417', '170,417,417,417,326,417,417,283,127,169,168,417,417,209,167,417,127', '326,417,417,162,209,209,209,209,326,417,257,209,209,127,256,417,291', '293,209,417,417,295,417,417,300,315,151,417,417,417,417,417,417,417', '4,4,417,230,4,4,318,4,209,205,146,322,323,325,231,144,136,134,333,234', '335,336,250,205,4,205,121,205,205,133,4,126,4,341,4,4,124,4,4,4,4,4', '4,4,4,353,248,4,4,205,355,4,111,109,4,4,108,121,105,205,205,121,4,104', '205,205,187,363,4,121,73,205,4,4,365,4,4,366,369,224,4,4,4,4,4,4,4,412', '412,4,374,412,412,66,412,205,120,187,247,241,65,187,383,384,386,387', '121,121,121,187,120,412,120,390,120,120,41,412,397,412,398,412,412,236', '412,412,412,404,412,412,412,412,236,40,412,412,120,8,412,5,414,412,412', '1,419,236,187,187,187,412,421,423,186,236,427,412,,,12,412,412,12,412', '412,158,,,412,412,412,412,412,412,412,253,253,412,158,253,253,,253,253', '186,,158,,186,,12,,,320,12,,186,,,253,158,,12,,,253,,253,,253,253,,253', '253,253,,253,253,253,253,,,253,253,320,,253,,320,253,253,320,,186,186', '186,320,253,,12,12,12,,253,,45,,253,253,,253,253,,,,253,253,253,253', '253,253,253,411,411,253,,411,411,,411,411,202,,320,320,320,45,,,,45', ',,,,202,411,202,45,202,202,,411,,411,,411,411,,411,411,411,,411,411', '411,411,,,411,411,202,,411,,,411,411,7,7,7,,,,411,45,45,45,,,411,,,202', '411,411,,411,411,,,,411,411,411,411,411,411,411,254,254,411,,254,254', ',254,254,204,,,,,,,,,7,7,7,7,,204,254,204,,204,204,,254,,254,,254,254', ',254,254,254,,254,254,254,254,,,254,254,204,,254,,,254,254,,,,204,204', ',254,,204,204,,,254,,,204,254,254,,254,254,,,,254,254,254,254,254,254', '254,10,10,254,,10,10,,10,204,208,,,,,,,,,,,,,,208,10,208,,208,208,,10', ',10,,10,10,,10,10,10,,10,10,10,10,,,10,10,208,,10,,,10,10,,,,,,,10,', '208,208,,,10,,,208,10,10,,10,10,,,,10,10,10,10,10,10,10,11,11,10,,11', '11,,11,,206,,,,,,,,,,,,,,206,11,206,,206,206,,11,,11,,11,11,,11,11,11', ',11,11,11,11,,,11,11,206,,11,,,11,11,,,,,,,11,,206,206,,,11,,,206,11', '11,,11,11,,,,11,11,11,11,11,11,11,255,255,11,207,255,255,,255,,203,', ',,,,,,207,,207,,207,207,203,255,203,,203,203,,255,,255,,255,255,,255', '255,255,,255,255,207,,,,255,255,203,,255,,,255,255,,,207,207,,,255,', ',207,,,255,,,203,255,255,,255,255,,,,255,255,255,255,255,255,255,15', '15,255,122,15,15,,15,,201,,,,,,,,122,,122,,122,122,201,15,201,,201,201', ',15,,15,,15,15,,15,15,15,,15,15,122,,,,15,15,201,,15,,,15,15,,,,,,,15', ',,,,,15,,,,15,15,,15,15,,,,15,15,15,15,15,15,15,16,16,15,123,16,16,', '16,,,,,,,,,,123,,123,,123,123,,16,,,,,,16,,16,,16,16,,16,16,16,,16,16', '123,,,,16,16,,,16,,,16,16,,,,,,,16,,,,,,16,,,,16,16,,16,16,,,,16,16', '16,16,16,16,16,17,17,16,,17,17,,17,,,,,,,,,,,,,,,,,17,,,,,,17,,17,,17', '17,,17,17,17,,17,17,,,,,17,17,,,17,,,17,17,,,,,,,17,,,,,,17,,,,17,17', ',17,17,,,,17,17,17,17,17,17,17,18,18,17,,18,18,,18,,,,,,,,,,,,,,,,,18', ',,,,,18,,18,,18,18,,18,18,18,,18,18,18,18,,,18,18,,,18,,,18,18,,,,,', ',18,,,,,,18,,,,18,18,,18,18,,,,18,18,18,18,18,18,18,409,409,18,,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,409,409,409,409,393,393,409,,393,393,,393', '393,,,,,,,,,,,,,,,,393,,,,,,393,,393,,393,393,,393,393,393,,393,393', '393,393,,,393,393,,,393,,,393,393,,,,,,,393,,,,,,393,,,,393,393,,393', '393,,,,393,393,393,393,393,393,393,42,42,393,,42,42,,42,,,,,,,,,,,,', ',,,,42,,,,,,42,,42,,42,42,,42,42,42,,42,42,,,,,42,42,,,42,,,42,42,,', ',,,,42,,,,,,42,,,,42,42,,42,42,,,,42,42,42,42,42,42,42,43,43,42,,43', '43,,43,,,,,,,,,,,,,,,,,43,,,,,,43,,43,,43,43,,43,43,43,,43,43,,,,,43', '43,,,43,,,43,43,,,,,,,43,,,,,,43,,,,43,43,,43,43,,,,43,43,43,43,43,43', '43,44,44,43,,44,44,,44,,,,,,,,,,,,,,,,,44,,,,,,44,,44,,44,44,,44,44', '44,,44,44,,,,,44,44,,,44,,,44,44,,,,,,,44,,,,,,44,,,,44,44,,44,44,,', ',44,44,44,44,44,44,44,260,260,44,,260,260,,260,,,,,,,,,,,,,,,,,260,', ',,,,260,,260,,260,260,,260,260,260,,260,260,260,260,,,260,260,,,260', ',,260,260,,,,,,,260,,,,,,260,,,,260,260,,260,260,,,,260,260,260,260', '260,260,260,264,264,260,,264,264,,264,264,,,,,,,,,,,,,,,,264,,,,,,264', ',264,,264,264,,264,264,264,,264,264,264,264,,,264,264,,,264,,,264,264', ',,,,,,264,,,,,,264,,,,264,264,,264,264,,,,264,264,264,264,264,264,264', '55,55,264,,55,55,55,55,,,,,,,,,,,,,,,,,55,,,,,,55,,55,,55,55,,55,55', '55,,55,55,55,55,,,55,55,,,55,,,55,55,,,,,,,55,,,,,,55,,,,55,55,,55,55', ',,,55,55,55,55,55,55,55,56,56,55,,56,56,56,56,,,,,,,,,,,,,,,,,56,,,', ',,56,,56,,56,56,,56,56,56,,56,56,56,56,,,56,56,,,56,,,56,56,,,,,,,56', ',,,,,56,,,,56,56,,56,56,,,,56,56,56,56,56,56,56,57,57,56,,57,57,,57', '57,,,,,,,,,,,,,,,,57,,,,,,57,,57,,57,57,,57,57,57,,57,57,57,57,,,57', '57,,,57,,,57,57,,,,,,,57,,,,,,57,,,,57,57,,57,57,,,,57,57,57,57,57,57', '57,63,63,57,,63,63,,63,,,,,,,,,,,,,,,,,63,,,,,,63,,63,,63,63,,63,63', '63,,63,63,63,63,,,63,63,,,63,,,63,63,,,,,,,63,,,,,,63,,,,63,63,,63,63', ',,,63,63,63,63,63,63,63,382,382,63,,382,382,,382,,,,,,,,,,,,,,,,,382', ',,,,,382,,382,,382,382,,382,382,382,,382,382,,,,,382,382,,,382,,,382', '382,,,,,,,382,,,,,,382,,,,382,382,,382,382,,,,382,382,382,382,382,382', '382,222,222,382,,222,222,,222,,,,,,,,,,,,,,,,,222,,,,,,222,,222,,222', '222,,222,222,222,,222,222,,,,,222,222,,,222,,,222,222,,,,,,,222,,,,', ',222,,,,222,222,,222,222,,,,222,222,222,222,222,222,222,68,68,222,,68', '68,,68,,,,,,,,,,,,,,,,,68,,,,,,68,,68,,68,68,,68,68,68,,68,68,,,,,68', '68,,,68,,,68,68,,,,,,,68,,,,,,68,,,,68,68,,68,68,,,,68,68,68,68,68,68', '68,370,370,68,,370,370,,370,,,,,,,,,,,,,,,,,370,,,,,,370,,370,,370,370', ',370,370,370,,370,370,370,370,,,370,370,,,370,,,370,370,,,,,,,370,,', ',,,370,,,,370,370,,370,370,,,,370,370,370,370,370,370,370,72,72,370', ',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,364,364,72,,364,364,,364,,,,,,,,,,,,,,,,,364,,,,,,364,,364', ',364,364,,364,364,364,,364,364,,,,,364,364,,,364,,,364,364,,,,,,,364', ',,,,,364,,,,364,364,,364,364,,,,364,364,364,364,364,364,364,74,74,364', ',74,74,,74,,,,,,,,,,,,,,,,,74,,,,,,74,,74,,74,74,,74,74,74,,74,74,74', '74,,,74,74,,,74,,,74,74,,,,,,,74,,,,,,74,,,,74,74,,74,74,,,,74,74,74', '74,74,74,74,75,75,74,,75,75,,75,,,,,,,,,,,,,,,,,75,,,,,,75,,75,,75,75', ',75,75,75,,75,75,75,75,,,75,75,,,75,,,75,75,,,,,,,75,,,,,,75,,,,75,75', ',75,75,,,,75,75,75,75,75,75,75,76,76,75,,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,76,77,77,76,,77,77', ',77,,,,,,,,,,,,,,,,,77,,,,,,77,,77,,77,77,,77,77,77,,77,77,77,77,,,77', '77,,,77,,,77,77,,,,,,,77,,,,,,77,,,,77,77,,77,77,,,,77,77,77,77,77,77', '77,78,78,77,,78,78,,78,,,,,,,,,,,,,,,,,78,,,,,,78,,78,,78,78,,78,78', '78,,78,78,78,78,,,78,78,,,78,,,78,78,,,,,,,78,,,,,,78,,,,78,78,,78,78', ',,,78,78,78,78,78,78,78,79,79,78,,79,79,,79,,,,,,,,,,,,,,,,,79,,,,,', '79,,79,,79,79,,79,79,79,,79,79,79,79,,,79,79,,,79,,,79,79,,,,,,,79,', ',,,,79,,,,79,79,,79,79,,,,79,79,79,79,79,79,79,316,316,79,,316,316,', '316,,,,,,,,,,,,,,,,,316,,,,,,316,,316,,316,316,,316,316,316,,316,316', ',,,,316,316,,,316,,,316,316,,,,,,,316,,,,,,316,,,,316,316,,316,316,', ',,316,316,316,316,316,316,316,81,81,316,,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,81,81,81,81,81,82,82,81,,82,82', ',82,,,,,,,,,,,,,,,,,82,,,,,,82,82,82,82,82,82,82,82,82,82,,82,82,,,', ',82,82,82,82,82,,,82,82,,,,,,,82,,,,,82,82,,,,82,82,,82,82,,,,82,82', '82,82,82,82,82,83,83,82,,83,83,,83,,,,,,,,,,,,,,,,,83,,,,,,83,,83,,83', '83,,83,83,83,,83,83,,,,,83,83,,,83,,,83,83,,,,,,,83,,,,,,83,,,,83,83', ',83,83,,,,83,83,83,83,83,83,83,84,84,83,,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,84,85,85,84,,85,85,,85', ',,,,,,,,,,,,,,,,85,,,,,,85,,85,,85,85,,85,85,85,,85,85,,,,,85,85,,,85', ',,85,85,,,,,,,85,,,,,,85,,,,85,85,,85,85,,,,85,85,85,85,85,85,85,86', '86,85,,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,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,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,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,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,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,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,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,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,95,95,94,,95,95,,95,,,,,,,,,,,,,,,,,95,,,,,,95,,95,,95', '95,,95,95,95,,95,95,,,,,95,95,,,95,,,95,95,,,,,,,95,,,,,,95,,,,95,95', ',95,95,,,,95,95,95,95,95,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,96,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,97,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,98,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,99,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', '100,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,101,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,102,102,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,103,103,103,103,103,103,265,265,103,,265,265,,265,,', ',,,,,,,,,,,,,,265,,,,,,265,,265,,265,265,,265,265,265,,265,265,265,265', ',,265,265,,,265,,,265,265,,,,,,,265,,,,,,265,,,,265,265,,265,265,,,', '265,265,265,265,265,265,265,179,179,265,,179,179,,179,,,,,,,,,,,,,,', ',,179,,,,,,179,,179,,179,179,,179,179,179,,179,179,,,,,179,179,,,179', ',,179,179,,,,,,,179,,,,,,179,,,,179,179,,179,179,,,,179,179,179,179', '179,179,179,106,106,179,,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,106,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,107,107,107,178,178,107,,178,178,,178', ',,,,,,,,,,,,,,,,178,,,,,,178,,178,,178,178,,178,178,178,,178,178,,,', ',178,178,,,178,,,178,178,,,,,,,178,,,,,,178,,,,178,178,,178,178,,,,178', '178,178,178,178,178,178,177,177,178,,177,177,,177,,,,,,,,,,,,,,,,,177', ',,,,,177,,177,,177,177,,177,177,177,,177,177,,,,,177,177,,,177,,,177', '177,,,,,,,177,,,,,,177,,,,177,177,,177,177,,,,177,177,177,177,177,177', '177,110,110,177,,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,110,110,110,173,173,110', ',173,173,,173,,,173,,,,,,,,,,,,,,173,,,,,,173,,173,,173,173,,173,173', '173,,173,173,,,,,173,173,,,173,,,173,173,,,,,,,173,,,,,,173,,,,173,173', ',173,173,,,,173,173,173,173,173,173,173,270,270,173,,270,270,,270,,', ',,,,,,,,,,,,,,270,,,,,,270,,270,,270,270,,270,270,270,,270,270,270,270', ',,270,270,,,270,,,270,270,,,,,,,270,,,,,,270,,,,270,270,,270,270,,,', '270,270,270,270,270,270,270,285,285,270,,285,285,,285,,,,,,,,,,,,,,', ',,285,,,,,,285,,285,,285,285,,285,285,285,,285,285,,,,,285,285,,,285', ',,285,285,,,,,,,285,,,,,,285,,,,285,285,,285,285,,,,285,285,285,285', '285,285,285,286,286,285,,286,286,,286,,,,,,,,,,,,,,,,,286,,,,,,286,', '286,,286,286,,286,286,286,,286,286,,,,,286,286,,,286,,,286,286,,,,,', ',286,,,,,,286,,,,286,286,,286,286,,,,286,286,286,286,286,286,286,287', '287,286,,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,160,160,287,,160,160,,160', '160,,,,,,,,,,,,,,,,160,,,,,,160,,160,,160,160,,160,160,160,,160,160', '160,160,,,160,160,,,160,,,160,160,,,,,,,160,,,,,,160,,,,160,160,,160', '160,,,,160,160,160,160,160,160,160,340,340,160,,340,340,,340,,,,,,,', ',,,,,,,,,340,,,,,,340,,340,,340,340,,340,340,340,,340,340,340,340,,', '340,340,,,340,,,340,340,,,,,,,340,,,,,,340,,,,340,340,,340,340,,,,340', '340,340,340,340,340,340,239,239,340,,239,239,,239,239,,,,,,,,,,,,,,', ',239,,,,,,239,,239,,239,239,,239,239,239,,239,239,239,239,,,239,239', ',,239,,,239,239,,,,,,,239,,,,,,239,,,,239,239,,239,239,,,,239,239,239', '239,239,239,239,128,128,239,,128,128,,128,,,,,,,,,,,,,,,,,128,128,,', ',,128,,128,,128,128,,128,128,128,,128,128,128,128,,,128,128,,,128,,', '128,128,,,,,,,128,,,,,,128,,,,128,128,,128,128,,,,128,128,128,128,128', '128,128,157,157,128,,157,157,,157,,,,,,,,,,,,,,,,,157,,,,,,157,,157', ',157,157,,157,157,157,,157,157,157,157,,,157,157,,,157,,,157,157,,,', ',,,157,,,,,,157,,,,157,157,,157,157,,,,157,157,157,157,157,157,157,289', '289,157,,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,290,290,289,,290,290,,290', ',,,,,,,,,,,,,,,,290,,,,,,290,,290,,290,290,,290,290,290,,290,290,,,', ',290,290,,,290,,,290,290,,,,,,,290,,,,,,290,,,,290,290,,290,290,,,,290', '290,290,290,290,290,290,338,338,290,,338,338,,338,338,,,,,,,,,,,,,,', ',338,,,,,,338,,338,,338,338,,338,338,338,,338,338,,,,,338,338,,,338', ',,338,338,,,,,,,338,,,,,,338,,,,338,338,,338,338,,,,338,338,338,338', '338,338,338,331,331,338,,331,331,,331,,,,,,,,,,,,,,,,,331,,,,,,331,', '331,,331,331,,331,331,331,,331,331,331,331,,,331,331,,,331,,,331,331', ',,,,,,331,,,,,,331,,,,331,331,,331,331,,,,331,331,331,331,331,331,331', '330,330,331,,330,330,,330,,,,,,,,,,,,,,,,,330,,,,,,330,,330,,330,330', ',330,330,330,,330,330,,,,,330,330,,,330,,,330,330,,,,,,,330,,,,,,330', ',,,330,330,,330,330,,,,330,330,330,330,330,330,330,327,327,330,,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,80,80,327,,80,80,,80,,,,,,,,,,,,,,,,', '80,258,258,,,258,80,,80,,80,80,,80,80,80,,80,80,80,80,,,80,80,,,80,', ',80,80,,,,,,,80,,258,,,258,80,,,,80,80,212,80,80,,,,80,80,80,80,80,80', '80,258,212,80,212,211,212,212,,,,258,258,,,,,,,211,,211,,211,211,,,', '212,,,,,,,,212,212,212,212,212,212,,212,212,211,,,,,212,,,211,211,211', '211,211,211,,211,211,,132,,132,,211,,,,,,,212,132,132,132,,132,,132', '132,,132,132,132,132,,46,46,211,,46,,,,,,,,,,132,,,,132,132,,,132,132', '132,132,132,132,,132,132,131,,131,,,132,,,46,,,46,131,131,131,,131,', '131,131,,131,131,131,131,,188,188,,132,188,46,,,,,,,,,131,46,46,,131', '131,,,131,131,131,131,131,131,,131,131,129,,129,,,131,,,188,,,188,129', '129,129,,129,,129,129,,129,129,129,129,,,,,131,,188,,,,,,,,,129,188', '188,,129,129,,,129,129,129,129,129,129,232,129,129,,,,,,129,,,,232,232', '232,232,232,,232,232,,232,232,232,232,,,,,,,,129,,,,,,,,232,,,,232,232', ',159,232,232,232,232,232,232,,232,232,,,159,159,159,232,159,,159,159', ',159,159,159,159,,,,,,,,,,,,,,,232,159,,,,159,159,,9,159,159,159,159', '159,159,,159,159,,,9,9,9,159,9,214,9,9,,9,9,9,9,,159,159,,,214,214,', '214,,214,214,,214,159,9,,,,9,9,,,9,9,9,9,9,9,,9,9,214,,,,,9,,,214,214', '214,214,214,214,,214,214,,171,,,,214,,,,,,,9,171,171,171,171,171,,171', '171,,171,171,171,171,,,,214,,,,,,,,,,,,171,,,,171,171,,362,171,171,171', '171,171,171,,171,171,,,362,362,362,171,362,,362,362,,362,362,362,362', ',,,,,,,,,,,,,,171,362,,,,362,362,,358,362,362,362,362,362,362,,362,362', ',,358,358,358,362,358,,358,358,,358,358,358,358,,,,,,,,,,,,,,,362,358', ',,,358,358,,227,358,358,358,358,358,358,,358,358,,,227,227,227,358,227', ',227,227,,227,227,227,227,,,,,,,,,,,,,,,358,227,,,,227,227,,199,227', '227,227,227,227,227,,227,227,,,199,199,199,227,199,213,199,199,,199', '199,199,199,,,,,,213,213,,213,,213,213,,213,227,199,,,,199,199,,,199', '199,199,199,199,199,,199,199,213,,,,,199,,,213,213,213,213,213,213,359', '213,213,,,,,,213,,,,359,359,359,199,359,,359,359,,359,359,359,359,,', ',,,,,213,,,,,,,,359,,,,359,359,,360,359,359,359,359,359,359,,359,359', ',,360,360,360,359,360,,360,360,,360,360,360,360,,,,,,,,,,,,,,,359,360', ',,,360,360,,361,360,360,360,360,360,360,,360,360,,,361,361,361,360,361', '215,361,361,,361,361,361,361,,,,,,215,215,,215,,215,215,,215,360,361', ',,,361,361,,,361,361,361,361,361,361,,361,361,215,,,,,361,,,215,215', '215,215,215,215,217,215,215,,,,,,215,,,,217,217,217,361,217,,217,217', ',217,217,217,217,,,,,,,,215,,,,,,,,217,,,,,,,,217,217,217,217,217,217', '219,217,217,,,219,,,217,,,,219,219,219,,219,,219,219,,219,219,219,219', ',,,,,,,217,,,,,,,,219,,,,219,219,,218,219,219,219,219,219,219,,219,219', ',,218,218,218,219,218,216,218,218,,218,218,218,218,,,,,,216,216,,216', ',216,216,,216,219,218,,,,,218,,,218,218,218,218,218,218,,218,218,216', ',,,,218,,,216,216,216,216,216,216,378,216,216,,,,,,216,,,,378,378,378', '218,378,,378,378,,378,378,378,378,343,343,,,343,,,216,,,,,,,,378,,,', '378,378,,,378,378,378,378,378,378,,378,378,,,,,,378,,343,,,343,,284', '284,284,284,,284,284,284,,284,,284,284,,,,,378,343,284,284,284,,,,284', ',,343,343,,,,,,,,284,284,,,,,,,,,,,,284,284,284,284,284,284,284,282', '282,282,282,,282,282,282,,282,,282,282,,,,,,,282,282,282,,,,282,,,,', ',,,,,,,282,282,,,,,,,,,,,,282,282,282,282,282,282,282,226,226,226,226', ',226,226,226,,226,,226,226,,,,,,,226,226,226,,,,226,,,,,,,,,,,,226,226', ',,,,,,,,,,,226,226,226,226,226,226,226,317,317,317,317,,317,317,317', ',317,,317,317,,,,,,,317,317,317,,,,317,,,,,,,,,,,,317,317,,,,,,,,,,', ',317,317,317,317,317,317,317' ] racc_action_check = arr = ::Array.new(8687, 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, 317, nil, nil, 172, 300, nil, 475, 302, 7712, 607, 694, 326, nil, nil, 868, 955, 1042, 1129, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 236, 264, 1390, 1477, 1564, 412, 7481, nil, nil, nil, nil, nil, nil, nil, nil, 1825, 1912, 1999, nil, nil, nil, nil, nil, 2086, nil, 198, 196, nil, 2347, nil, nil, 29, 2521, 231, 2695, 2782, 2869, 2956, 3043, 3130, 7306, 3304, 3391, 3478, 3565, 3652, 3739, 3826, 3913, 4000, 4087, 4174, 4261, 4348, 4435, 4522, 4609, 4696, 4783, 4870, 4957, 5044, 5131, 5218, 196, 223, 5479, 5566, 221, 218, 5827, 200, nil, nil, nil, nil, nil, nil, nil, nil, 263, 194, 866, 953, 183, nil, 196, 82, 6610, 7564, nil, 7507, 7450, 177, 178, nil, 169, nil, nil, nil, nil, nil, nil, nil, 176, nil, 171, nil, 80, nil, nil, 154, nil, 99, nil, nil, 95, 6697, 302, 7665, 6349, nil, 130, nil, nil, nil, nil, 128, 124, 123, 114, 7787, 93, 5914, 54, 55, 62, 5740, 5653, 5392, nil, nil, 24, 9, 7, 2, 320, 234, 7538, -1, -2, -4, -9, nil, nil, nil, nil, nil, nil, 7975, 18, 872, 437, 785, 524, 176, 698, 779, 611, 89, 2, 7392, 7375, 7992, 7729, 8157, 8329, 8211, 8312, 8265, nil, nil, 2260, 86, 182, nil, 8541, 7928, 57, nil, 153, 146, 7618, nil, 155, nil, 270, nil, nil, 6523, nil, 260, nil, nil, nil, nil, nil, 256, 184, -24, 189, -11, nil, 346, 520, 781, 145, 112, 7331, -28, 1651, 32, 71, nil, 1738, 5305, nil, nil, nil, nil, 6001, nil, nil, nil, nil, 94, nil, nil, nil, nil, 101, 110, 8484, 120, 8427, 6088, 6175, 6262, nil, 6784, 6871, 143, nil, 130, nil, 148, nil, nil, nil, nil, 152, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 155, 3217, 8598, 171, nil, 360, nil, 172, 176, nil, 116, 91, 7219, nil, nil, 7132, 7045, nil, 182, 67, 184, 162, -7, 6958, nil, 6436, 198, nil, 8413, nil, nil, nil, nil, nil, nil, nil, nil, nil, 148, -11, 211, nil, nil, 7881, 8046, 8093, 8140, 7834, 231, 2608, 177, 241, nil, nil, 239, 2434, nil, nil, nil, 237, nil, nil, nil, 8383, 9, -15, nil, 2173, 267, 244, nil, 269, 270, nil, nil, 277, nil, nil, 1303, nil, nil, nil, 282, 252, nil, nil, nil, nil, nil, 291, nil, nil, nil, nil, 1216, nil, 433, 259, nil, 304, nil, nil, 85, nil, 308, nil, 314, nil, 315, nil, nil, nil, 286, nil, nil, nil, nil ] racc_action_default = [ -3, -252, -1, -2, -4, -5, -8, -10, -16, -21, -252, -252, -252, -33, -34, -252, -252, -252, -252, -61, -62, -63, -64, -65, -66, -67, -68, -69, -70, -71, -72, -73, -74, -75, -76, -77, -78, -79, -80, -81, -86, -90, -252, -252, -252, -252, -252, -182, -183, -184, -185, -186, -187, -188, -189, -252, -252, -252, -200, -201, -202, -203, -204, -252, -206, -252, -219, -222, -252, -227, -228, -252, -252, -7, -252, -252, -252, -252, -252, -252, -252, -252, -126, -252, -252, -252, -252, -252, -252, -252, -252, -252, -252, -252, -252, -252, -252, -252, -252, -252, -252, -252, -252, -252, -252, -121, -251, -251, -22, -23, -252, -251, -139, -160, -161, -162, -163, -164, -165, -166, -46, -252, -47, -54, -252, -87, -252, -252, -252, -252, -97, -252, -252, -251, -229, -148, -150, -151, -152, -153, -154, -156, -157, -14, -229, -191, -229, -193, -252, -196, -197, -252, -205, -252, -210, -213, -252, -217, -252, -252, -252, 432, -6, -9, -11, -12, -13, -17, -18, -19, -20, -252, -229, -252, -79, -80, -81, -240, -246, -234, -127, -130, -187, -188, -189, -252, -237, -235, -243, -183, -184, -185, -186, -233, -238, -239, -241, -242, -244, -59, -252, -36, -37, -38, -39, -40, -41, -42, -43, -44, -45, -48, -49, -50, -51, -52, -53, -55, -56, -252, -57, -115, -252, -229, -83, -91, -126, -125, -252, -124, -252, -231, -252, -28, -251, -167, -252, -58, -92, -252, -95, -229, -170, -172, -173, -174, -175, -177, -252, -252, -180, -252, -89, -252, -252, -252, -252, -251, -230, -252, -230, -252, -252, -194, -252, -252, -207, -208, -209, -211, -252, -214, -215, -216, -218, -229, -220, -223, -225, -226, -8, -252, -126, -252, -230, -252, -252, -252, -35, -252, -252, -229, -117, -252, -85, -229, -129, -131, -132, -133, -252, -234, -235, -236, -237, -240, -243, -245, -246, -247, -248, -249, -250, -122, -123, -252, -232, -126, -252, -142, -252, -168, -229, -252, -94, -252, -230, -252, -178, -179, -252, -252, -88, -252, -100, -252, -106, -252, -252, -110, -251, -252, -158, -252, -149, -155, -15, -190, -192, -195, -198, -199, -212, -252, -252, -229, -26, -128, -136, -134, -135, -60, -119, -252, -230, -82, -252, -25, -29, -229, -251, -143, -144, -145, -252, -93, -96, -171, -176, -252, -100, -99, -252, -252, -106, -105, -252, -252, -109, -111, -252, -140, -141, -252, -159, -221, -224, -252, -30, -116, -118, -84, -120, -27, -252, -169, -181, -98, -101, -252, -104, -252, -251, -137, -252, -147, -24, -31, -138, -252, -103, -252, -108, -252, -113, -114, -146, -231, -102, -107, -112, -32 ] racc_goto_table = [ 2, 120, 122, 123, 125, 124, 137, 231, 139, 235, 133, 200, 152, 257, 172, 221, 250, 315, 241, 344, 339, 277, 390, 261, 234, 262, 73, 423, 129, 131, 132, 235, 228, 230, 338, 291, 408, 357, 108, 109, 238, 385, 143, 143, 151, 381, 256, 250, 220, 275, 154, 283, 404, 368, 159, 318, 371, 144, 146, 341, 397, 163, 164, 165, 166, 345, 377, 329, 171, 199, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 410, 143, 407, 227, 227, 162, 328, 232, 350, 271, 272, 3, 269, 293, 389, 394, 223, 167, 168, 169, 170, 270, 268, 155, 157, 273, 143, 1, nil, nil, nil, 325, nil, nil, nil, nil, 250, nil, 322, nil, nil, 251, nil, 319, nil, nil, 292, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 280, 137, nil, 139, 133, nil, nil, 353, 342, nil, 274, 295, 122, 281, nil, nil, 129, 131, 132, nil, nil, nil, nil, 363, nil, nil, nil, 366, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 294, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 374, nil, nil, nil, nil, nil, nil, 219, nil, nil, nil, nil, 431, 355, 250, 396, nil, 137, 352, 139, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 398, nil, nil, nil, 392, 323, nil, 199, nil, nil, nil, nil, nil, 346, 403, 369, nil, 151, 351, 333, 335, nil, nil, 154, 379, nil, nil, nil, nil, nil, nil, nil, nil, nil, 392, nil, nil, 358, 359, 360, nil, 361, 362, 400, nil, nil, nil, nil, nil, nil, nil, 372, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 232, 137, nil, 139, nil, nil, nil, nil, 424, nil, nil, 378, 427, nil, 199, nil, 346, nil, nil, nil, nil, nil, 199, nil, nil, nil, nil, 401, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 219, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 129, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 414, nil, nil, nil, nil, nil, nil, nil, nil, nil, 232, nil, nil, nil, nil, nil, 419, nil, 421, 425 ] racc_goto_check = [ 2, 10, 10, 10, 37, 6, 35, 13, 34, 49, 57, 19, 80, 15, 11, 50, 36, 14, 42, 65, 47, 88, 59, 15, 58, 15, 5, 48, 10, 10, 10, 49, 53, 53, 46, 51, 43, 54, 9, 9, 41, 45, 6, 6, 6, 44, 58, 36, 20, 42, 6, 15, 59, 16, 10, 61, 62, 8, 8, 64, 12, 6, 6, 6, 6, 67, 68, 69, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 45, 6, 44, 10, 10, 5, 71, 10, 75, 76, 77, 3, 81, 15, 47, 65, 8, 9, 9, 9, 9, 82, 84, 85, 86, 87, 6, 1, nil, nil, nil, 15, nil, nil, nil, nil, 36, nil, 42, nil, nil, 8, nil, 49, nil, nil, 50, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 6, 35, nil, 34, 57, nil, nil, 15, 49, nil, 2, 11, 10, 2, nil, nil, 10, 10, 10, nil, nil, nil, nil, 15, nil, nil, nil, 15, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 37, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 15, nil, nil, nil, nil, nil, nil, 10, nil, nil, nil, nil, 14, 11, 36, 88, nil, 35, 80, 34, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 15, nil, nil, nil, 49, 2, nil, 10, nil, nil, nil, nil, nil, 6, 15, 11, nil, 6, 6, 2, 2, nil, nil, 6, 19, nil, nil, nil, nil, nil, nil, nil, nil, nil, 49, nil, nil, 10, 10, 10, nil, 10, 10, 50, nil, nil, nil, nil, nil, nil, nil, 57, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 10, 35, nil, 34, nil, nil, nil, nil, 49, nil, nil, 10, 13, nil, 10, nil, 6, nil, nil, nil, nil, nil, 10, nil, nil, nil, nil, 37, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 10, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 10, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 2, nil, nil, nil, nil, nil, nil, nil, nil, nil, 10, nil, nil, nil, nil, nil, 2, nil, 2, 2 ] racc_goto_pointer = [ nil, 116, 0, 100, nil, 22, -13, nil, 2, 28, -14, -68, -295, -103, -214, -121, -263, nil, nil, -72, -54, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, -38, -40, -111, -36, nil, nil, nil, -86, -109, -346, -289, -295, -221, -235, -385, -102, -87, -187, nil, -74, -247, nil, nil, -35, -87, -318, nil, -179, -264, nil, -198, -239, nil, -194, -260, -182, nil, -153, nil, nil, nil, -167, -58, -57, nil, nil, -51, -52, -43, nil, -42, 47, 47, -43, -138 ] racc_goto_default = [ nil, nil, 391, nil, 4, 5, 6, 7, nil, 8, 9, nil, nil, nil, nil, nil, 233, 13, 14, 337, nil, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, nil, 40, 41, 126, nil, nil, 130, nil, nil, nil, nil, nil, 229, nil, nil, 105, nil, 180, 185, 181, 112, nil, nil, 111, nil, nil, 134, nil, 135, 136, 140, 242, 243, 244, 245, 246, 249, 148, 150, 58, 59, 60, 63, nil, nil, nil, 153, nil, nil, nil, nil, nil ] racc_reduce_table = [ 0, 0, :racc_error, 1, 95, :_reduce_1, 1, 95, :_reduce_2, 0, 95, :_reduce_3, 1, 96, :_reduce_4, 1, 98, :_reduce_5, 3, 98, :_reduce_6, 2, 98, :_reduce_7, 1, 99, :_reduce_8, 3, 99, :_reduce_9, 1, 100, :_reduce_none, 3, 100, :_reduce_11, 3, 100, :_reduce_12, 3, 100, :_reduce_13, 1, 102, :_reduce_14, 3, 102, :_reduce_15, 1, 101, :_reduce_none, 3, 101, :_reduce_17, 3, 101, :_reduce_18, 3, 101, :_reduce_19, 3, 101, :_reduce_20, 1, 103, :_reduce_none, 2, 103, :_reduce_22, 2, 103, :_reduce_23, 7, 103, :_reduce_24, 5, 103, :_reduce_25, 5, 103, :_reduce_26, 4, 110, :_reduce_27, 1, 107, :_reduce_28, 3, 107, :_reduce_29, 1, 106, :_reduce_30, 2, 106, :_reduce_31, 4, 106, :_reduce_32, 1, 104, :_reduce_none, 1, 104, :_reduce_none, 4, 104, :_reduce_35, 3, 104, :_reduce_36, 3, 104, :_reduce_37, 3, 104, :_reduce_38, 3, 104, :_reduce_39, 3, 104, :_reduce_40, 3, 104, :_reduce_41, 3, 104, :_reduce_42, 3, 104, :_reduce_43, 3, 104, :_reduce_44, 3, 104, :_reduce_45, 2, 104, :_reduce_46, 2, 104, :_reduce_47, 3, 104, :_reduce_48, 3, 104, :_reduce_49, 3, 104, :_reduce_50, 3, 104, :_reduce_51, 3, 104, :_reduce_52, 3, 104, :_reduce_53, 2, 104, :_reduce_54, 3, 104, :_reduce_55, 3, 104, :_reduce_56, 3, 104, :_reduce_57, 3, 104, :_reduce_58, 1, 113, :_reduce_59, 3, 113, :_reduce_60, 1, 111, :_reduce_none, 1, 111, :_reduce_none, 1, 111, :_reduce_none, 1, 111, :_reduce_none, 1, 111, :_reduce_none, 1, 111, :_reduce_none, 1, 111, :_reduce_none, 1, 111, :_reduce_none, 1, 111, :_reduce_none, 1, 111, :_reduce_none, 1, 111, :_reduce_none, 1, 111, :_reduce_none, 1, 111, :_reduce_none, 1, 111, :_reduce_none, 1, 111, :_reduce_none, 1, 111, :_reduce_none, 1, 111, :_reduce_77, 1, 111, :_reduce_78, 1, 111, :_reduce_79, 1, 111, :_reduce_80, 1, 111, :_reduce_81, 5, 112, :_reduce_82, 3, 112, :_reduce_83, 6, 112, :_reduce_84, 4, 112, :_reduce_85, 1, 116, :_reduce_86, 2, 116, :_reduce_87, 4, 132, :_reduce_88, 3, 132, :_reduce_89, 1, 132, :_reduce_90, 3, 133, :_reduce_91, 2, 131, :_reduce_92, 3, 135, :_reduce_93, 2, 135, :_reduce_94, 2, 134, :_reduce_95, 4, 134, :_reduce_96, 2, 119, :_reduce_97, 5, 137, :_reduce_98, 4, 137, :_reduce_99, 0, 138, :_reduce_none, 2, 138, :_reduce_101, 4, 138, :_reduce_102, 3, 138, :_reduce_103, 6, 120, :_reduce_104, 5, 120, :_reduce_105, 0, 139, :_reduce_none, 4, 139, :_reduce_107, 3, 139, :_reduce_108, 5, 118, :_reduce_109, 1, 140, :_reduce_110, 2, 140, :_reduce_111, 5, 141, :_reduce_112, 1, 142, :_reduce_none, 1, 142, :_reduce_none, 1, 114, :_reduce_none, 4, 114, :_reduce_116, 1, 145, :_reduce_117, 3, 145, :_reduce_118, 3, 144, :_reduce_119, 6, 117, :_reduce_120, 2, 117, :_reduce_121, 3, 146, :_reduce_122, 3, 146, :_reduce_123, 1, 147, :_reduce_none, 1, 147, :_reduce_none, 0, 105, :_reduce_126, 1, 105, :_reduce_127, 3, 105, :_reduce_128, 1, 149, :_reduce_none, 1, 149, :_reduce_none, 1, 149, :_reduce_none, 1, 149, :_reduce_none, 1, 149, :_reduce_none, 3, 148, :_reduce_134, 3, 148, :_reduce_135, 3, 148, :_reduce_136, 6, 121, :_reduce_137, 7, 122, :_reduce_138, 1, 154, :_reduce_139, 1, 153, :_reduce_none, 1, 153, :_reduce_none, 1, 155, :_reduce_none, 2, 155, :_reduce_143, 1, 156, :_reduce_none, 1, 156, :_reduce_none, 7, 123, :_reduce_146, 6, 123, :_reduce_147, 1, 157, :_reduce_148, 3, 157, :_reduce_149, 1, 159, :_reduce_none, 1, 159, :_reduce_none, 1, 159, :_reduce_152, 1, 159, :_reduce_none, 1, 160, :_reduce_154, 3, 160, :_reduce_155, 1, 161, :_reduce_none, 1, 161, :_reduce_none, 1, 158, :_reduce_none, 2, 158, :_reduce_159, 1, 151, :_reduce_none, 1, 151, :_reduce_none, 1, 151, :_reduce_none, 1, 151, :_reduce_none, 1, 151, :_reduce_none, 1, 151, :_reduce_none, 1, 151, :_reduce_166, 1, 152, :_reduce_167, 2, 152, :_reduce_168, 4, 152, :_reduce_169, 1, 136, :_reduce_170, 3, 136, :_reduce_171, 1, 162, :_reduce_none, 1, 162, :_reduce_none, 1, 163, :_reduce_none, 1, 163, :_reduce_none, 3, 165, :_reduce_176, 1, 165, :_reduce_177, 2, 166, :_reduce_178, 2, 164, :_reduce_179, 1, 167, :_reduce_180, 4, 167, :_reduce_181, 1, 115, :_reduce_182, 1, 125, :_reduce_183, 1, 125, :_reduce_184, 1, 125, :_reduce_185, 1, 125, :_reduce_186, 1, 125, :_reduce_187, 1, 125, :_reduce_188, 1, 125, :_reduce_189, 4, 126, :_reduce_190, 2, 126, :_reduce_191, 4, 126, :_reduce_192, 2, 126, :_reduce_193, 3, 127, :_reduce_194, 4, 127, :_reduce_195, 2, 127, :_reduce_196, 1, 168, :_reduce_197, 3, 168, :_reduce_198, 3, 169, :_reduce_199, 1, 129, :_reduce_none, 1, 129, :_reduce_none, 1, 129, :_reduce_none, 1, 170, :_reduce_203, 1, 170, :_reduce_204, 2, 171, :_reduce_205, 1, 173, :_reduce_206, 1, 175, :_reduce_207, 1, 176, :_reduce_208, 2, 174, :_reduce_209, 1, 177, :_reduce_210, 1, 178, :_reduce_211, 2, 178, :_reduce_212, 2, 172, :_reduce_213, 2, 179, :_reduce_214, 2, 179, :_reduce_215, 3, 97, :_reduce_216, 0, 181, :_reduce_none, 1, 181, :_reduce_none, 0, 180, :_reduce_219, 2, 180, :_reduce_220, 4, 180, :_reduce_221, 1, 124, :_reduce_222, 3, 124, :_reduce_223, 5, 124, :_reduce_224, 1, 182, :_reduce_none, 1, 182, :_reduce_none, 1, 130, :_reduce_227, 1, 128, :_reduce_228, 0, 109, :_reduce_none, 1, 109, :_reduce_230, 0, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 150, :_reduce_none, 1, 150, :_reduce_none, 1, 150, :_reduce_none, 1, 150, :_reduce_none, 1, 150, :_reduce_none, 1, 150, :_reduce_none, 1, 150, :_reduce_none, 1, 150, :_reduce_none, 1, 150, :_reduce_none, 1, 150, :_reduce_none, 1, 150, :_reduce_none, 1, 150, :_reduce_none, 1, 150, :_reduce_none, 1, 150, :_reduce_none, 1, 150, :_reduce_none, 1, 150, :_reduce_none, 1, 150, :_reduce_none, 1, 150, :_reduce_none, 0, 143, :_reduce_251 ] racc_reduce_n = 252 racc_shift_n = 432 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, :LPAREN => 26, :RPAREN => 27, :ISEQUAL => 28, :GREATEREQUAL => 29, :GREATERTHAN => 30, :LESSTHAN => 31, :IF => 32, :ELSE => 33, :DEFINE => 34, :ELSIF => 35, :VARIABLE => 36, :CLASS => 37, :INHERITS => 38, :NODE => 39, :BOOLEAN => 40, :NAME => 41, :SEMIC => 42, :CASE => 43, :DEFAULT => 44, :AT => 45, :ATAT => 46, :LCOLLECT => 47, :RCOLLECT => 48, :CLASSREF => 49, :NOT => 50, :OR => 51, :AND => 52, :UNDEF => 53, :PARROW => 54, :PLUS => 55, :MINUS => 56, :TIMES => 57, :DIV => 58, :LSHIFT => 59, :RSHIFT => 60, :UMINUS => 61, :MATCH => 62, :NOMATCH => 63, :REGEX => 64, :IN_EDGE => 65, :OUT_EDGE => 66, :IN_EDGE_SUB => 67, :OUT_EDGE_SUB => 68, :IN => 69, :UNLESS => 70, :PIPE => 71, :LAMBDA => 72, :SELBRACE => 73, :NUMBER => 74, :HEREDOC => 75, :SUBLOCATE => 76, :RENDER_STRING => 77, :RENDER_EXPR => 78, :EPP_START => 79, :EPP_END => 80, :EPP_END_TRIM => 81, :FUNCTION => 82, :PRIVATE => 83, :ATTR => 84, :TYPE => 85, :APPLICATION_R => 86, :CONSUMES_R => 87, :PRODUCES_R => 88, :LOW => 89, :HIGH => 90, :LISTSTART => 91, :SPLAT => 92, :MODULO => 93 } racc_nt_base = 94 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", "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", "PRIVATE", "ATTR", "TYPE", "APPLICATION_R", "CONSUMES_R", "PRODUCES_R", "LOW", "HIGH", "LISTSTART", "SPLAT", "MODULO", "$start", "program", "statements", "epp_expression", "syntactic_statements", "syntactic_statement", "assignment", "relationship", "assignments", "resource", "expression", "attribute_operations", "additional_resource_bodies", "resource_bodies", "endsemi", "endcomma", "resource_body", "primary_expression", "call_function_expression", "expressions", "selector_entries", "variable", "call_method_with_lambda_expression", "collection_expression", "case_expression", "if_expression", "unless_expression", "definition_expression", "hostclass_expression", "node_definition_expression", "epp_render_expression", "reserved_word", "array", "hash", "regex", "quotedtext", "type", "lambda", "call_method_expression", "named_access", "lambda_parameter_list", "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", "stacked_classname", "classparent", "classnameordefault", "hostnames", "nodeparent", "hostname", "dotted_name", "name_or_number", "parameter", "untyped_parameter", "typed_parameter", "regular_parameter", "splat_parameter", "parameter_type", "hashpairs", "hashpair", "string", "dq_string", "heredoc", "dqpre", "dqrval", "dqpost", "dqmid", "text_expression", "dqtail", "sublocated_text", "epp_parameters_list", "optional_statements", "epp_end" ] Racc_debug_parser = false ##### State transition tables end ##### # reduce 0 omitted module_eval(<<'.,.,', 'egrammar.ra', 66) def _reduce_1(val, _values, result) result = create_program(Factory.block_or_expression(*val[0])) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 67) def _reduce_2(val, _values, result) result = create_program(Factory.block_or_expression(*val[0])) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 68) def _reduce_3(val, _values, result) result = create_empty_program() result end .,., module_eval(<<'.,.,', 'egrammar.ra', 72) def _reduce_4(val, _values, result) result = transform_calls(val[0]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 79) def _reduce_5(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 80) def _reduce_6(val, _values, result) result = val[0].push val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 81) def _reduce_7(val, _values, result) result = val[0].push val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 88) def _reduce_8(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 89) 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', 94) def _reduce_11(val, _values, result) result = val[0].set(val[2]) ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 95) def _reduce_12(val, _values, result) result = val[0].plus_set(val[2]) ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 96) def _reduce_13(val, _values, result) result = val[0].minus_set(val[2]); loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 99) def _reduce_14(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 100) def _reduce_15(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 16 omitted module_eval(<<'.,.,', 'egrammar.ra', 104) def _reduce_17(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 105) def _reduce_18(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 106) 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', 107) def _reduce_20(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., # reduce 21 omitted module_eval(<<'.,.,', 'egrammar.ra', 116) def _reduce_22(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', 127) def _reduce_23(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', 138) def _reduce_24(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', 145) def _reduce_25(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', 154) def _reduce_26(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]) 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', 176) def _reduce_27(val, _values, result) result = Factory.RESOURCE_BODY(val[0], val[2]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 179) def _reduce_28(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 180) def _reduce_29(val, _values, result) result = val[0].push val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 186) def _reduce_30(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 187) def _reduce_31(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 188) def _reduce_32(val, _values, result) result = val[2] result end .,., # reduce 33 omitted # reduce 34 omitted module_eval(<<'.,.,', 'egrammar.ra', 195) def _reduce_35(val, _values, result) result = val[0][*val[2]] ; loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 196) def _reduce_36(val, _values, result) result = val[0].in val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 197) def _reduce_37(val, _values, result) result = val[0] =~ val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 198) def _reduce_38(val, _values, result) result = val[0].mne val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 199) def _reduce_39(val, _values, result) result = val[0] + val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 200) def _reduce_40(val, _values, result) result = val[0] - val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 201) def _reduce_41(val, _values, result) result = val[0] / val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 202) def _reduce_42(val, _values, result) result = val[0] * val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 203) def _reduce_43(val, _values, result) result = val[0] % val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 204) def _reduce_44(val, _values, result) result = val[0] << val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 205) def _reduce_45(val, _values, result) result = val[0] >> val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 206) def _reduce_46(val, _values, result) result = val[1].minus() ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 207) def _reduce_47(val, _values, result) result = val[1].unfold() ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 208) def _reduce_48(val, _values, result) result = val[0].ne val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 209) def _reduce_49(val, _values, result) result = val[0] == val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 210) def _reduce_50(val, _values, result) result = val[0] > val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 211) def _reduce_51(val, _values, result) result = val[0] >= val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 212) def _reduce_52(val, _values, result) result = val[0] < val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 213) def _reduce_53(val, _values, result) result = val[0] <= val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 214) def _reduce_54(val, _values, result) result = val[1].not ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 215) def _reduce_55(val, _values, result) result = val[0].and val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 216) def _reduce_56(val, _values, result) result = val[0].or val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 217) def _reduce_57(val, _values, result) result = val[0].select(*val[2]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 218) def _reduce_58(val, _values, result) result = val[1].paren() ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 228) def _reduce_59(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 229) def _reduce_60(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 61 omitted # reduce 62 omitted # reduce 63 omitted # reduce 64 omitted # reduce 65 omitted # reduce 66 omitted # reduce 67 omitted # reduce 68 omitted # reduce 69 omitted # reduce 70 omitted # reduce 71 omitted # reduce 72 omitted # reduce 73 omitted # reduce 74 omitted # reduce 75 omitted # reduce 76 omitted module_eval(<<'.,.,', 'egrammar.ra', 248) def _reduce_77(val, _values, result) result = Factory.NUMBER(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 249) def _reduce_78(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 250) def _reduce_79(val, _values, result) result = Factory.literal(:default) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 251) def _reduce_80(val, _values, result) result = Factory.literal(:undef) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 252) def _reduce_81(val, _values, result) result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 261) def _reduce_82(val, _values, result) result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 265) def _reduce_83(val, _values, result) result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 269) def _reduce_84(val, _values, result) result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] result.lambda = val[5] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 274) def _reduce_85(val, _values, result) result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] result.lambda = val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 282) def _reduce_86(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 283) def _reduce_87(val, _values, result) result = val[0]; val[0].lambda = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 286) def _reduce_88(val, _values, result) result = Factory.CALL_METHOD(val[0], val[2]); loc result, val[1], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 287) def _reduce_89(val, _values, result) result = Factory.CALL_METHOD(val[0], []); loc result, val[1], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 288) def _reduce_90(val, _values, result) result = Factory.CALL_METHOD(val[0], []); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 292) def _reduce_91(val, _values, result) result = val[0].dot(Factory.fqn(val[2][:value])) loc result, val[1], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 300) def _reduce_92(val, _values, result) result = Factory.LAMBDA(val[0][:value], val[1][:value]) loc result, val[0][:start], val[1][:end] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 305) def _reduce_93(val, _values, result) result = {:end => val[2], :value =>val[1] } result end .,., module_eval(<<'.,.,', 'egrammar.ra', 306) def _reduce_94(val, _values, result) result = {:end => val[1], :value => nil } result end .,., module_eval(<<'.,.,', 'egrammar.ra', 310) def _reduce_95(val, _values, result) result = {:start => val[0], :value => [] } result end .,., module_eval(<<'.,.,', 'egrammar.ra', 311) def _reduce_96(val, _values, result) result = {:start => val[0], :value => val[1] } result end .,., module_eval(<<'.,.,', 'egrammar.ra', 319) def _reduce_97(val, _values, result) result = val[1] loc(result, val[0], val[1]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 326) def _reduce_98(val, _values, result) result = Factory.IF(val[0], Factory.block_or_expression(*val[2]), val[4]) loc(result, val[0], (val[4] ? val[4] : val[3])) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 330) def _reduce_99(val, _values, result) result = Factory.IF(val[0], nil, val[3]) loc(result, val[0], (val[3] ? val[3] : val[2])) result end .,., # reduce 100 omitted module_eval(<<'.,.,', 'egrammar.ra', 338) def _reduce_101(val, _values, result) result = val[1] loc(result, val[0], val[1]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 342) def _reduce_102(val, _values, result) result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 346) def _reduce_103(val, _values, result) result = nil # don't think a nop is needed here either result end .,., module_eval(<<'.,.,', 'egrammar.ra', 353) def _reduce_104(val, _values, result) result = Factory.UNLESS(val[1], Factory.block_or_expression(*val[3]), val[5]) loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 357) def _reduce_105(val, _values, result) result = Factory.UNLESS(val[1], nil, val[4]) loc result, val[0], val[4] result end .,., # reduce 106 omitted module_eval(<<'.,.,', 'egrammar.ra', 367) def _reduce_107(val, _values, result) result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 371) def _reduce_108(val, _values, result) result = nil # don't think a nop is needed here either result end .,., module_eval(<<'.,.,', 'egrammar.ra', 378) def _reduce_109(val, _values, result) result = Factory.CASE(val[1], *val[3]) loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 384) def _reduce_110(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 385) def _reduce_111(val, _values, result) result = val[0].push val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 390) def _reduce_112(val, _values, result) result = Factory.WHEN(val[0], val[3]); loc result, val[1], val[4] result end .,., # reduce 113 omitted # reduce 114 omitted # reduce 115 omitted module_eval(<<'.,.,', 'egrammar.ra', 406) def _reduce_116(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 411) def _reduce_117(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 412) def _reduce_118(val, _values, result) result = val[0].push val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 417) def _reduce_119(val, _values, result) result = Factory.MAP(val[0], val[2]) ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 427) def _reduce_120(val, _values, result) result = Factory.COLLECT(val[0], val[1], val[3]) loc result, val[0], val[5] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 431) def _reduce_121(val, _values, result) result = Factory.COLLECT(val[0], val[1], []) loc result, val[0], val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 436) def _reduce_122(val, _values, result) result = Factory.VIRTUAL_QUERY(val[1]) ; loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 437) def _reduce_123(val, _values, result) result = Factory.EXPORTED_QUERY(val[1]) ; loc result, val[0], val[2] result end .,., # reduce 124 omitted # reduce 125 omitted module_eval(<<'.,.,', 'egrammar.ra', 446) def _reduce_126(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 447) def _reduce_127(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 448) def _reduce_128(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 129 omitted # reduce 130 omitted # reduce 131 omitted # reduce 132 omitted # reduce 133 omitted module_eval(<<'.,.,', 'egrammar.ra', 467) def _reduce_134(val, _values, result) result = Factory.ATTRIBUTE_OP(val[0][:value], :'=>', val[2]) loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 471) def _reduce_135(val, _values, result) result = Factory.ATTRIBUTE_OP(val[0][:value], :'+>', val[2]) loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 475) def _reduce_136(val, _values, result) result = Factory.ATTRIBUTES_OP(val[2]) ; loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 484) def _reduce_137(val, _values, result) result = add_definition(Factory.DEFINITION(classname(val[1][:value]), val[2], val[4])) loc result, val[0], val[5] # 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', 498) def _reduce_138(val, _values, result) # Remove this class' name from the namestack as all nested classes have been parsed namepop result = add_definition(Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), val[5])) loc result, val[0], val[6] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 508) def _reduce_139(val, _values, result) namestack(val[0][:value]) ; result = val[0] result end .,., # reduce 140 omitted # reduce 141 omitted # reduce 142 omitted module_eval(<<'.,.,', 'egrammar.ra', 517) def _reduce_143(val, _values, result) result = val[1] result end .,., # reduce 144 omitted # reduce 145 omitted module_eval(<<'.,.,', 'egrammar.ra', 534) def _reduce_146(val, _values, result) result = add_definition(Factory.NODE(val[1], val[3], val[5])) loc result, val[0], val[6] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 538) def _reduce_147(val, _values, result) result = add_definition(Factory.NODE(val[1], val[3], nil)) loc result, val[0], val[5] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 548) def _reduce_148(val, _values, result) result = [result] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 549) def _reduce_149(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 150 omitted # reduce 151 omitted module_eval(<<'.,.,', 'egrammar.ra', 556) def _reduce_152(val, _values, result) result = Factory.literal(:default); loc result, val[0] result end .,., # reduce 153 omitted module_eval(<<'.,.,', 'egrammar.ra', 560) def _reduce_154(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 561) def _reduce_155(val, _values, result) result = Factory.concat(val[0], '.', val[2][:value]); loc result, val[0], val[2] result end .,., # reduce 156 omitted # reduce 157 omitted # reduce 158 omitted module_eval(<<'.,.,', 'egrammar.ra', 570) def _reduce_159(val, _values, result) result = val[1] result end .,., # reduce 160 omitted # reduce 161 omitted # reduce 162 omitted # reduce 163 omitted # reduce 164 omitted # reduce 165 omitted module_eval(<<'.,.,', 'egrammar.ra', 592) def _reduce_166(val, _values, result) error val[0], "'class' is not a valid classname" result end .,., module_eval(<<'.,.,', 'egrammar.ra', 596) def _reduce_167(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 597) def _reduce_168(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 598) def _reduce_169(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 602) def _reduce_170(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 603) def _reduce_171(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 172 omitted # reduce 173 omitted # reduce 174 omitted # reduce 175 omitted module_eval(<<'.,.,', 'egrammar.ra', 615) def _reduce_176(val, _values, result) result = Factory.PARAM(val[0][:value], val[2]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 616) def _reduce_177(val, _values, result) result = Factory.PARAM(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 619) def _reduce_178(val, _values, result) result = val[1]; val[1].captures_rest() result end .,., module_eval(<<'.,.,', 'egrammar.ra', 622) def _reduce_179(val, _values, result) val[1].type_expr(val[0]) ; result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 625) def _reduce_180(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 626) def _reduce_181(val, _values, result) result = val[0][*val[2]] ; loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 631) def _reduce_182(val, _values, result) result = Factory.fqn(val[0][:value]).var ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 636) def _reduce_183(val, _values, result) result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 637) def _reduce_184(val, _values, result) result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 638) def _reduce_185(val, _values, result) result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 639) def _reduce_186(val, _values, result) result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 640) def _reduce_187(val, _values, result) result = Factory.RESERVED(val[0][:value], true) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 641) def _reduce_188(val, _values, result) result = Factory.RESERVED(val[0][:value], true) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 642) def _reduce_189(val, _values, result) result = Factory.RESERVED(val[0][:value], true) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 648) def _reduce_190(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 649) def _reduce_191(val, _values, result) result = Factory.literal([]) ; loc result, val[0], val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 650) def _reduce_192(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 651) def _reduce_193(val, _values, result) result = Factory.literal([]) ; loc result, val[0], val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 654) def _reduce_194(val, _values, result) result = Factory.HASH(val[1]); loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 655) def _reduce_195(val, _values, result) result = Factory.HASH(val[1]); loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 656) def _reduce_196(val, _values, result) result = Factory.literal({}) ; loc result, val[0], val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 659) def _reduce_197(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 660) def _reduce_198(val, _values, result) result = val[0].push val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 663) def _reduce_199(val, _values, result) result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] result end .,., # reduce 200 omitted # reduce 201 omitted # reduce 202 omitted module_eval(<<'.,.,', 'egrammar.ra', 671) def _reduce_203(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 672) def _reduce_204(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 674) def _reduce_205(val, _values, result) result = Factory.string(val[0], *val[1]) ; loc result, val[0], val[1][-1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 675) def _reduce_206(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 676) def _reduce_207(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 677) def _reduce_208(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 678) def _reduce_209(val, _values, result) result = [val[0]] + val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 679) def _reduce_210(val, _values, result) result = Factory.TEXT(val[0]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 682) def _reduce_211(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 683) def _reduce_212(val, _values, result) result = [val[0]] + val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 686) def _reduce_213(val, _values, result) result = Factory.HEREDOC(val[0][:value], val[1]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 689) def _reduce_214(val, _values, result) result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 690) def _reduce_215(val, _values, result) result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 693) def _reduce_216(val, _values, result) result = Factory.EPP(val[1], val[2]); loc result, val[0] result end .,., # reduce 217 omitted # reduce 218 omitted module_eval(<<'.,.,', 'egrammar.ra', 700) def _reduce_219(val, _values, result) result = nil result end .,., module_eval(<<'.,.,', 'egrammar.ra', 701) def _reduce_220(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 702) def _reduce_221(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 705) def _reduce_222(val, _values, result) result = Factory.RENDER_STRING(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 706) def _reduce_223(val, _values, result) result = Factory.RENDER_EXPR(val[1]); loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 707) def _reduce_224(val, _values, result) result = Factory.RENDER_EXPR(Factory.block_or_expression(*val[2])); loc result, val[0], val[4] result end .,., # reduce 225 omitted # reduce 226 omitted module_eval(<<'.,.,', 'egrammar.ra', 713) def _reduce_227(val, _values, result) result = Factory.QREF(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 716) def _reduce_228(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., # reduce 229 omitted module_eval(<<'.,.,', 'egrammar.ra', 722) def _reduce_230(val, _values, result) result = nil result end .,., # reduce 231 omitted # reduce 232 omitted # reduce 233 omitted # reduce 234 omitted # reduce 235 omitted # 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 # reduce 245 omitted # reduce 246 omitted # reduce 247 omitted # reduce 248 omitted # reduce 249 omitted # reduce 250 omitted module_eval(<<'.,.,', 'egrammar.ra', 749) def _reduce_251(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-3.8.5/lib/puppet/pops/parser/epp_parser.rb���������������������������������������������������0000664�0052762�0001160�00000003170�12650174557�022000� 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()# {:mode => :epp}) 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-3.8.5/lib/puppet/pops/parser/epp_support.rb��������������������������������������������������0000664�0052762�0001160�00000023257�12650174557�022230� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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 Puppet::Pops::Parser::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(Puppet::Pops::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(Puppet::Pops::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(Puppet::Pops::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(Puppet::Pops::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 %> respetively 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 = Puppet::Pops::Issues::EPP_UNBALANCED_EXPRESSION else mode = :epp 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 = Puppet::Pops::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 �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/parser/evaluating_parser.rb��������������������������������������������0000664�0052762�0001160�00000010331�12650174557�023350� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� # Does not support "import" and parsing ruby files # class Puppet::Pops::Parser::EvaluatingParser attr_reader :parser def initialize() @parser = Puppet::Pops::Parser::Parser.new() end def parse_string(s, file_source = 'unknown') @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)) 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)) end def evaluate_string(scope, s, file_source='unknown') evaluate(scope, parse_string(s, file_source)) end def evaluate_file(file) evaluate(parse_file(file)) end def clear() @acceptor = nil end # Create a closure that can be called in the given scope def closure(model, scope) Puppet::Pops::Evaluator::Closure.new(evaluator, model, scope) end def evaluate(scope, model) return nil unless model evaluator.evaluate(model, scope) end def evaluator # Do not use the cached evaluator if this is a migration run if (Puppet.lookup(:migration_checker) { nil }) return Puppet::Pops::Evaluator::EvaluatorImpl.new() end @@evaluator ||= Puppet::Pops::Evaluator::EvaluatorImpl.new() @@evaluator end def convert_to_3x(object, scope) val = evaluator.convert(object, scope, nil) end def validate(parse_result) resulting_acceptor = acceptor() validator(resulting_acceptor).validate(parse_result) resulting_acceptor end def acceptor() Puppet::Pops::Validation::Acceptor.new end def validator(acceptor) Puppet::Pops::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? or parse_result.source_ref == '' parse_result.source_ref = @file_source end validation_result = validate(parse_result) Puppet::Pops::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 < Puppet::Pops::Parser::EvaluatingParser def initialize() @parser = Puppet::Pops::Parser::EppParser.new() end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/parser/heredoc_support.rb����������������������������������������������0000664�0052762�0001160�00000012467�12650174557�023056� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops::Parser::HeredocSupport include Puppet::Pops::Parser::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(Puppet::Pops::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(Puppet::Pops::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(Puppet::Pops::Issues::HEREDOC_MISSING_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(Puppet::Pops::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(Puppet::Pops::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(Puppet::Pops::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(Puppet::Pops::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 = Puppet::Pops::Parser::Locator::SubLocator.sub_locator(str, locator.file, 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, Puppet::Pops::Parser::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$/, '') if remove_break result end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/parser/interpolation_support.rb����������������������������������������0000664�0052762�0001160�00000017325�12650174557�024332� 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/strucures) 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 scn.skip(self.class::PATTERN_WS) queue_size = queue.size until scn.eos? do if token = lex_token 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] 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-3.8.5/lib/puppet/pops/parser/lexer2.rb�������������������������������������������������������0000664�0052762�0001160�00000057456�12650174557�021061� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The Lexer is responsbile 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' class Puppet::Pops::Parser::Lexer2 include Puppet::Pops::Parser::LexerSupport include Puppet::Pops::Parser::HeredocSupport include Puppet::Pops::Parser::InterpolationSupport include Puppet::Pops::Parser::SlurpSupport include Puppet::Pops::Parser::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_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], # The following tokens exist in reserved form. Later they will be made # live subject to a feature switch. "application" => [:APPLICATION_R, 'application', 11], "consumes" => [:CONSUMES_R, 'consumes', 8], "produces" => [:PRODUCES_R, 'produces', 8], } 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]+} # The single line comment includes the line ending. PATTERN_COMMENT = %r{#.*\r?} PATTERN_MLCOMMENT = %r{/\*(.*?)\*/}m PATTERN_REGEX = %r{/[^/\n]*/} 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_BSLASH = '\\'.freeze attr_reader :locator def initialize() 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, '') end def lex_string(string, path='') initvars @scanner = StringScanner.new(string) @locator = Puppet::Pops::Parser::Locator.locator(string, path) end # Lexes an unquoted string. # @param string [String] the string to lex # @param locator [Puppet::Pops::Parser::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 @scanner = StringScanner.new(string) @locator = locator || Puppet::Pops::Parser::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) : "" @scanner = StringScanner.new(contents.freeze) @locator = Puppet::Pops::Parser::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, } 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, 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 # 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_without_pos(Puppet::Pops::Issues::NO_INPUT_TO_LEXER) unless scn scn.skip(PATTERN_WS) # This is the lexer's main loop until queue.empty? && scn.eos? do if token = queue.shift || lex_token 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 # Using three char look ahead (may be faster to do 2 char look ahead since only 2 tokens require a third scn = @scanner ctx = @lexing_context before = @scanner.pos # A look ahead of 3 characters is used since the longest operator ambiguity is resolved at that point. # PERFORMANCE NOTE: It is faster to peek once and use three separate variables for lookahead 0, 1 and 2. # la = scn.peek(3) return nil if la.empty? # Ruby 1.8.7 requires using offset and length (or integers are returned. # PERFORMANCE NOTE. # It is slightly faster to use these local variables than accessing la[0], la[1] etc. in ruby 1.9.3 # But not big enough to warrant two completely different implementations. # la0 = la[0,1] la1 = la[1,1] la2 = la[2,1] # PERFORMANCE NOTE: # A case when, where all the cases are literal values is the fastest way to map from data to code. # It is much faster than using a hash with lambdas, hash with symbol used to then invoke send etc. # This case statement is evaluated for most character positions in puppet source, and great care must # be taken to not introduce performance regressions. # case la0 when '.' emit(TOKEN_DOT, before) when ',' emit(TOKEN_COMMA, before) when '[' if (before == 0 || scn.string[locator.char_offset(before)-1,1] =~ /[[:blank:]\r\n]+/) emit(TOKEN_LISTSTART, before) else emit(TOKEN_LBRACK, before) end when ']' emit(TOKEN_RBRACK, before) when '(' emit(TOKEN_LPAREN, before) when ')' emit(TOKEN_RPAREN, before) when ';' emit(TOKEN_SEMIC, before) when '?' emit(TOKEN_QMARK, before) when '*' emit(TOKEN_TIMES, before) when '%' if la1 == '>' && ctx[:epp_mode] scn.pos += 2 if ctx[:epp_mode] == :expr enqueue_completed(TOKEN_EPPEND, before) end ctx[:epp_mode] = :text interpolate_epp else emit(TOKEN_MODULO, before) end when '{' # 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. # ctx[:brace_count] += 1 emit(if ctx[:after] == :QMARK TOKEN_SELBRACE else TOKEN_LBRACE end, before) when '}' ctx[:brace_count] -= 1 emit(TOKEN_RBRACE, before) # TOKENS @, @@, @( when '@' case la1 when '@' emit(TOKEN_ATAT, before) # TODO; Check if this is good for the grammar when '(' heredoc else emit(TOKEN_AT, before) end # TOKENS |, |>, |>> when '|' emit(case la1 when '>' la2 == '>' ? TOKEN_RRCOLLECT : TOKEN_RCOLLECT else TOKEN_PIPE end, before) # TOKENS =, =>, ==, =~ when '=' emit(case la1 when '=' TOKEN_ISEQUAL when '>' TOKEN_FARROW when '~' TOKEN_MATCH else TOKEN_EQUALS end, before) # TOKENS '+', '+=', and '+>' when '+' emit(case la1 when '=' TOKEN_APPENDS when '>' TOKEN_PARROW else TOKEN_PLUS end, before) # TOKENS '-', '->', and epp '-%>' (end of interpolation with trim) when '-' if ctx[:epp_mode] && la1 == '%' && la2 == '>' scn.pos += 3 if ctx[:epp_mode] == :expr enqueue_completed(TOKEN_EPPEND_TRIM, before) end interpolate_epp(:with_trim) else emit(case la1 when '>' TOKEN_IN_EDGE when '=' TOKEN_DELETES else TOKEN_MINUS end, before) end # TOKENS !, !=, !~ when '!' emit(case la1 when '=' TOKEN_NOTEQUAL when '~' TOKEN_NOMATCH else TOKEN_NOT end, before) # TOKENS ~>, ~ when '~' emit(la1 == '>' ? TOKEN_IN_EDGE_SUB : TOKEN_TILDE, before) when '#' scn.skip(PATTERN_COMMENT) nil # TOKENS '/', '/*' and '/ regexp /' when '/' case la1 when '*' scn.skip(PATTERN_MLCOMMENT) nil else # regexp position is a regexp, else a div if regexp_acceptable? && value = scn.scan(PATTERN_REGEX) # Ensure an escaped / was not matched while value[-2..-2] == STRING_BSLASH_BSLASH # i.e. \\ value += scn.scan_until(PATTERN_REGEX_END) 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 # TOKENS <, <=, <|, <<|, <<, <-, <~ when '<' emit(case la1 when '<' if la2 == '|' 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, before) # TOKENS >, >=, >> when '>' emit(case la1 when '>' TOKEN_RSHIFT when '=' TOKEN_GREATEREQUAL else TOKEN_GREATERTHAN end, before) # TOKENS :, ::CLASSREF, ::NAME when ':' if la1 == ':' before = scn.pos # 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. # if la2 >= 'A' && la2 <= 'Z' # CLASSREF or error value = scn.scan(PATTERN_CLASSREF) if value 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(Puppet::Pops::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(Puppet::Pops::Issues::ILLEGAL_FULLY_QUALIFIED_NAME) end end else emit(TOKEN_COLON, before) end when '$' 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 when '"' # 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 when "'" emit_completed([:STRING, slurp_sqstring.freeze, scn.pos - before], before) when '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' value = scn.scan(PATTERN_NUMBER) if value length = scn.pos - before assert_numeric(value, length) emit_completed([:NUMBER, value.freeze, length], before) else # move to faulty position ([0-9] was ok) scn.pos = scn.pos + 1 lex_error(Puppet::Pops::Issues::ILLEGAL_NUMBER) end when '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', '_' value = scn.scan(PATTERN_BARE_WORD) if value && value =~ PATTERN_NAME emit_completed(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(Puppet::Pops::Issues::ILLEGAL_FULLY_QUALIFIED_NAME) else lex_error(Puppet::Pops::Issues::ILLEGAL_NAME_OR_BARE_WORD) end end when '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' value = scn.scan(PATTERN_CLASSREF) if value 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(Puppet::Pops::Issues::ILLEGAL_CLASS_REFERENCE) end when "\n" # If heredoc_cont is in effect there are heredoc text lines to skip over # otherwise just skip the newline. # if ctx[:newline_jump] scn.pos = ctx[:newline_jump] ctx[:newline_jump] = nil else scn.pos += 1 end return nil when ' ', "\t", "\r" scn.skip(PATTERN_WS) return nil else # 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). if scn.skip(PATTERN_WS) nil else # "unrecognized char" emit([:OTHER, la0, 1], before) end end 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 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/parser/lexer_support.rb������������������������������������������������0000664�0052762�0001160�00000007661�12650174557�022564� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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 Puppet::Pops::Parser::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) 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 [Puppet::Pops::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) 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, length) if value =~ /^0[xX].*$/ lex_error(Puppet::Pops::Issues::INVALID_HEX_NUMBER, {:value => value}, length) unless value =~ /^0[xX][0-9A-Fa-f]+$/ elsif value =~ /^0[^.].*$/ lex_error(Puppet::Pops::Issues::INVALID_OCTAL_NUMBER, {:value => value}, length) unless value =~ /^0[0-7]+$/ else lex_error(Puppet::Pops::Issues::INVALID_DECIMAL_NUMBER, {:value => value}, length) unless value =~ /0?\d+(?:\.\d+)?(?:[eE]-?\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 < Puppet::Pops::Parser::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 parsser 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 end �������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/parser/locatable.rb����������������������������������������������������0000664�0052762�0001160�00000000737�12650174557�021574� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Interface for something that is "locateable" (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-3.8.5/lib/puppet/pops/parser/locator.rb������������������������������������������������������0000664�0052762�0001160�00000021567�12650174557�021315� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Helper class that keeps track of where line breaks are located and can answer questions about positions. # class Puppet::Pops::Parser::Locator RUBY_1_9_3 = (1 << 16 | 9 << 8 | 3) RUBY_2_0_0 = (2 << 16 | 0 << 8 | 0) RUBYVER_ARRAY = RUBY_VERSION.split(".").collect {|s| s.to_i } RUBYVER = (RUBYVER_ARRAY[0] << 16 | RUBYVER_ARRAY[1] << 8 | RUBYVER_ARRAY[2]) # Computes a symbol representing which ruby runtime this is running on # This implementation will fail if there are more than 255 minor or micro versions of ruby # def self.locator_version if RUBYVER >= RUBY_2_0_0 :ruby20 elsif RUBYVER >= RUBY_1_9_3 :ruby19 else :ruby18 end end LOCATOR_VERSION = locator_version # Constant set to true if multibyte is supported (includes multibyte extended regular expressions) MULTIBYTE = !!(LOCATOR_VERSION == :ruby19 || LOCATOR_VERSION == :ruby20) # 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) case LOCATOR_VERSION when :ruby20, :ruby19 Locator19.new(string, file, index) else Locator18.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 # 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 reported offset def char_length(offset, end_offset) 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 # 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 < Puppet::Pops::Parser::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(Puppet::Pops::Parser::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 private class AbstractLocator < Puppet::Pops::Parser::Locator attr_accessor :line_index attr_accessor :string attr_accessor :prev_offset attr_accessor :prev_line 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, index = nil) @string = string.freeze @file = file.freeze @prev_offset = nil @prev_line = nil @line_index = index compute_line_index unless !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 # 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 Locator18 < 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 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 # 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 offseta def char_length(offset, end_offset) string.byteslice(offset, end_offset - offset).length end end end �����������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/parser/parser_support.rb�����������������������������������������������0000664�0052762�0001160�00000020132�12650174557�022725� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/functions' require 'puppet/parser/files' require 'puppet/resource/type_collection' require 'puppet/resource/type_collection_helper' require 'puppet/resource/type' require 'monitor' # 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 Puppet::Pops::Parser::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 = Puppet::Pops::Model::Factory Model = Puppet::Pops::Model include Puppet::Resource::TypeCollectionHelper 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? t = t.current if t.respond_to?(:current) return t.value if t.is_a? Model::QualifiedName # else it is a lexer token t[:value] 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) semantic = semantic.current() if semantic.is_a?(Puppet::Pops::Model::Factory) # Adapt the model so it is possible to get location information. # The model may not have been added to the source tree, so give it the lexer's locator # directly instead of searching for the root Program where the locator is normally stored. # if semantic.is_a?(Puppet::Pops::Model::Positioned) adapter = Puppet::Pops::Adapters::SourcePosAdapter.adapt(semantic) adapter.locator = @lexer.locator else adapter = nil end except = Puppet::ParseError.new(message) except.file = @lexer.locator.file except.line = adapter.line if adapter except.pos = adapter.pos if adapter 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 = Puppet::Pops::Parser::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 file' else value_at = "'#{value[:value]}'" end error = Puppet::Pops::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, issue_code = Puppet::Pops::Issues::SYNTAX_ERROR.issue_code) end # Parses a String of pp DSL code. # def parse_string(code, path = '') @lexer.lex_string(code, path) _parse() end # Mark the factory wrapped model object with location information # @return [Puppet::Pops::Model::Factory] the given factory # @api private # def loc(factory, start_locateable, end_locateable = nil) factory.record_position(start_locateable, end_locateable) end # Mark the factory wrapped heredoc model object with location information # @return [Puppet::Pops::Model::Factory] the given factory # @api private # def heredoc_loc(factory, start_locateabke, end_locateable = 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.current definition 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 Puppet::Pops::Model::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?(Puppet::Pops::Model::QualifiedName) error(e.args[1], "attempt to pass argument list to the function '#{e.name_expr.value}' which cannot be called without parentheses") 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) Factory.transform_resource_wo_title(left, resource) 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_sym, 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 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/parser/slurp_support.rb������������������������������������������������0000664�0052762�0001160�00000010432�12650174557�022600� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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 Puppet::Pops::Parser::SlurpSupport include Puppet::Pops::Parser::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(Puppet::Pops::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(Puppet::Pops::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 last = scn.matched ignore = true 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 # 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') str.gsub!(/\\u(?:([\da-fA-F]{4})|\{([\da-fA-F]{1,6})\})/m) { [($1 || $2).hex].pack("U") } 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(Puppet::Pops::Issues::ILLEGAL_UNICODE_ESCAPE) "\\u" when "\n" ; '' when "\r\n"; '' else ch end else lex_warning(Puppet::Pops::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(Puppet::Pops::Issues::ILLEGAL_UNICODE_ESCAPE) else raise e end end str end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/patterns.rb������������������������������������������������������������0000664�0052762�0001160�00000004043�12650174557�020204� 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} # 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} # 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} # 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. 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-3.8.5/lib/puppet/pops/semantic_error.rb������������������������������������������������������0000664�0052762�0001160�00000001202�12650174557�021352� 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 end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/types/�����������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017161� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/types/class_loader.rb��������������������������������������������������0000664�0052762�0001160�00000010502�12650174557�022140� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'rgen/metamodel_builder' # 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 Puppet::Pops::Types::ClassLoader @autoloader = Puppet::Util::Autoload.new("ClassLoader", "", :wrap => false) # 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>, Puppet::Pops::Types::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 Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PType provide_from_type(name) else raise ArgumentError, "Cannot provide a class from a '#{name.class.name}'" end end private def self.provide_from_type(type) case type when Puppet::Pops::Types::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 Puppet::Pops::Types::PBooleanType # There is no other thing to load except this Enum meta type RGen::MetamodelBuilder::MMBase::Boolean when Puppet::Pops::Types::PType # TODO: PType should has 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 Puppet::Pops::Type::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 Puppet::Pops::Types::PArrayType ; Array when Puppet::Pops::Types::PTupleType ; Array when Puppet::Pops::Types::PHashType ; Hash when Puppet::Pops::Types::PStructType ; Hash when Puppet::Pops::Types::PRegexpType ; Regexp when Puppet::Pops::Types::PIntegerType ; Integer when Puppet::Pops::Types::PStringType ; String when Puppet::Pops::Types::PPatternType ; String when Puppet::Pops::Types::PEnumType ; String when Puppet::Pops::Types::PFloatType ; Float when Puppet::Pops::Types::PUndefType ; NilClass when Puppet::Pops::Types::PCallableType ; Proc else nil end end def self.provide_from_string(name) name_path = name.split('::') # always from the root, so remove an empty first segment if name_path[0].empty? name_path = name_path[1..-1] end 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?(Class) # Attempt to load it using the auto loader loaded_path = nil if paths_for_name(name).find {|path| loaded_path = path; @autoloader.load(path) } result = find_class(name_path) unless result.is_a?(Class) raise RuntimeError, "Loading of #{name} using relative path: '#{loaded_path}' did not create expected class" end end end return nil unless result.is_a?(Class) result end def self.find_class(name_path) name_path.reduce(Object) do |ns, name| begin ns.const_get(name) rescue NameError return nil end end end def self.paths_for_name(fq_name) [de_camel(fq_name), downcased_path(fq_name)] end def self.downcased_path(fq_name) fq_name.to_s.gsub(/::/, '/').downcase end 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 end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/types/enumeration.rb���������������������������������������������������0000664�0052762�0001160�00000001661�12650174557�022041� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The Enumeration class provides default Enumerable::Enumerator creation for Puppet Programming Language # runtime objects that supports the concept of enumeration. # class Puppet::Pops::Types::Enumeration # Produces an Enumerable::Enumerator for Array, Hash, Integer, Integer Range, and String. # def self.enumerator(o) @@singleton ||= new @@singleton.enumerator(o) end # Produces an Enumerator for Array, Hash, Integer, Integer Range, and String. # def enumerator(o) case o when String x = o.chars # Ruby 1.8.7 returns Enumerable::Enumerator, Ruby 1.8.9 Enumerator, and 2.0.0 an Array x.is_a?(Array) ? x.each : x when Integer o.times when Array o.each when Hash o.each when Puppet::Pops::Types::PIntegerType # Not enumerable if representing an infinite range return nil if o.to.nil? || o.from.nil? o.each else nil end end end �������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/types/type_calculator.rb�����������������������������������������������0000664�0052762�0001160�00000161751�12650174557�022714� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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 {Puppet::Pops::Types type model} are used to denote a specific type. It is most convenient # to use the {Puppet::Pops::Types::TypeFactory 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 Puppet::Pops::Types::TypeFactory TypeFactory for how to create instances of types # @see Puppet::Pops::Types::TypeParser TypeParser how to construct a type instance from a String # @see Puppet::Pops::Types 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 Puppet::Pops::Types::TypeCalculator Types = Puppet::Pops::Types TheInfinity = 1.0 / 0.0 # because the Infinity symbol is not defined # @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 [Puppet::Pops::Types::PCallableType] - the callable # @param args [Puppet::Pops::Types::PArrayType, Puppet::Pops::Types::PTupleType] args optionally including a lambda callable at the end # @return [Boolan] true if the callable accepts the arguments # # @api public def self.callable?(callable, args) singleton.callable?(callable, args) end # Produces a String representation of the given type. # @param t [Puppet::Pops::Types::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 # @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.debug_string(t) singleton.debug_string(t) end # @api public def self.enumerable(t) singleton.enumerable(t) end # @api private def self.singleton() @tc_instance ||= new end # @api public # def initialize @@assignable_visitor ||= Puppet::Pops::Visitor.new(nil,"assignable",1,1) @@infer_visitor ||= Puppet::Pops::Visitor.new(nil,"infer",0,0) @@infer_set_visitor ||= Puppet::Pops::Visitor.new(nil,"infer_set",0,0) @@instance_of_visitor ||= Puppet::Pops::Visitor.new(nil,"instance_of",1,1) @@string_visitor ||= Puppet::Pops::Visitor.new(nil,"string",0,0) @@inspect_visitor ||= Puppet::Pops::Visitor.new(nil,"debug_string",0,0) @@enumerable_visitor ||= Puppet::Pops::Visitor.new(nil,"enumerable",0,0) @@extract_visitor ||= Puppet::Pops::Visitor.new(nil,"extract",0,0) @@generalize_visitor ||= Puppet::Pops::Visitor.new(nil,"generalize",0,0) @@callable_visitor ||= Puppet::Pops::Visitor.new(nil,"callable",1,1) da = Types::PArrayType.new() da.element_type = Types::PDataType.new() @data_array = da h = Types::PHashType.new() h.element_type = Types::PDataType.new() h.key_type = Types::PScalarType.new() @data_hash = h @data_t = Types::PDataType.new() @scalar_t = Types::PScalarType.new() @numeric_t = Types::PNumericType.new() @t = Types::PAnyType.new() # Data accepts a Tuple that has 0-infinity Data compatible entries (e.g. a Tuple equivalent to Array). data_tuple = Types::PTupleType.new() data_tuple.addTypes(Types::PDataType.new()) data_tuple.size_type = Types::PIntegerType.new() data_tuple.size_type.from = 0 data_tuple.size_type.to = nil # infinity @data_tuple_t = data_tuple # Variant type compatible with Data data_variant = Types::PVariantType.new() data_variant.addTypes(@data_hash.copy) data_variant.addTypes(@data_array.copy) data_variant.addTypes(Types::PScalarType.new) data_variant.addTypes(Types::PUndefType.new) data_variant.addTypes(@data_tuple_t.copy) @data_variant_t = data_variant collection_default_size = Types::PIntegerType.new() collection_default_size.from = 0 collection_default_size.to = nil # infinity @collection_default_size_t = collection_default_size non_empty_string = Types::PStringType.new non_empty_string.size_type = Types::PIntegerType.new() non_empty_string.size_type.from = 1 non_empty_string.size_type.to = nil # infinity @non_empty_string_t = non_empty_string @nil_t = Types::PUndefType.new end # Convenience method to get a data type for comparisons # @api private the returned value may not be contained in another element # def data @data_t end # Convenience method to get a variant compatible with the Data type. # @api private the returned value may not be contained in another element # def data_variant @data_variant_t end def self.data_variant singleton.data_variant end # Answers the question 'is it possible to inject an instance of the given class' # A class is injectable if it has a special *assisted inject* class method called `inject` taking # an injector and a scope as argument, or if it has a zero args `initialize` method. # # @param klazz [Class, PRuntimeType] the class/type to check if it is injectable # @return [Class, nil] the injectable Class, or nil if not injectable # @api public # def injectable_class(klazz) # Handle case when we get a PType instead of a class if klazz.is_a?(Types::PRuntimeType) klazz = Puppet::Pops::Types::ClassLoader.provide(klazz) end # data types can not be injected (check again, it is not safe to assume that given RubyRuntime klazz arg was ok) return false unless type(klazz).is_a?(Types::PRuntimeType) if (klazz.respond_to?(:inject) && klazz.method(:inject).arity() == -4) || klazz.instance_method(:initialize).arity() == 0 klazz else nil end 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?(Class) t = type(t) end if t2.is_a?(Class) t2 = type(t2) end t2_class = t2.class # Unit can be assigned to anything return true if t2_class == Types::PUnitType if t2_class == Types::PVariantType # Assignable if all contained types are assignable t2.types.all? { |vt| @@assignable_visitor.visit_this_1(self, t, vt) } else # Turn NotUndef[T] into T when T is not assignable from Undef if t2_class == Types::PNotUndefType && !(t2.type.nil? || assignable?(t2.type, @nil_t)) assignable?(t, t2.type) else @@assignable_visitor.visit_this_1(self, t, t2) end end end # Returns an enumerable if the t represents something that can be iterated def enumerable(t) @@enumerable_visitor.visit_this_0(self, t) end # Answers, does the given callable accept the arguments given in args (an array or a tuple) # def callable?(callable, args) return false if !self.class.is_kind_of_callable?(callable) # Note that polymorphism is for the args type, the callable is always a callable @@callable_visitor.visit_this_1(self, args, callable) end # Answers if the two given types describe the same type def equals(left, right) return false unless left.is_a?(Types::PAnyType) && right.is_a?(Types::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 [Class] the class for which a puppet type is wanted # @api public # def type(c) raise ArgumentError, "Argument must be a Class" unless c.is_a? Class # Can't use a visitor here since we don't have an instance of the class case when c <= Integer type = Types::PIntegerType.new() when c == Float type = Types::PFloatType.new() when c == Numeric type = Types::PNumericType.new() when c == String type = Types::PStringType.new() when c == Regexp type = Types::PRegexpType.new() when c == NilClass type = Types::PUndefType.new() when c == FalseClass, c == TrueClass type = Types::PBooleanType.new() when c == Class type = Types::PType.new() when c == Array # Assume array of data values type = Types::PArrayType.new() type.element_type = Types::PDataType.new() when c == Hash # Assume hash with scalar keys and data values type = Types::PHashType.new() type.key_type = Types::PScalarType.new() type.element_type = Types::PDataType.new() else type = Types::PRuntimeType.new(:runtime => :ruby, :runtime_type_name => c.name) end type end # Generalizes value specific types. The given type is mutated and returned. # @api public def generalize!(o) @@generalize_visitor.visit_this_0(self, o) o.eAllContents.each { |x| @@generalize_visitor.visit_this_0(self, x) } o end def generalize_Object(o) # do nothing, there is nothing to change for most types end # @return [Boolean] true if the given argument is contained in a struct element key def is_struct_element_key?(o) c = o.eContainer if c.is_a?(Types::POptionalType) o = c c = c.eContainer end c.is_a?(Types::PStructElement) && c.key_type.equal?(o) end private :is_struct_element_key? def generalize_PStringType(o) # Skip generalization if the string is contained in a PStructElement key. unless is_struct_element_key?(o) o.values = [] o.size_type = nil end end def generalize_PCollectionType(o) # erase the size constraint from Array and Hash (if one exists, it is transformed to -Infinity - + Infinity, which is # not desirable. o.size_type = nil end def generalize_PFloatType(o) o.to = nil o.from = nil end def generalize_PIntegerType(o) o.to = nil o.from = nil 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) @@infer_visitor.visit_this_0(self, o) end def infer_generic(o) result = generalize!(infer(o)) result end # Answers 'what is the set of Puppet Types of o' # @api public # def infer_set(o) @@infer_set_visitor.visit_this_0(self, o) end def instance_of(t, o) @@instance_of_visitor.visit_this_1(self, t, o) end def instance_of_Object(t, o) # Undef is Undef and Any, but nothing else when checking instance? return false if (o.nil?) && t.class != Types::PAnyType assignable?(t, infer(o)) end # Anything is an instance of Unit # @api private def instance_of_PUnitType(t, o) true end def instance_of_PArrayType(t, o) return false unless o.is_a?(Array) return false unless o.all? {|element| instance_of(t.element_type, element) } size_t = t.size_type || @collection_default_size_t # optimize by calling directly return instance_of_PIntegerType(size_t, o.size) end # @api private def instance_of_PIntegerType(t, o) return false unless o.is_a?(Integer) x = t.from x = -Float::INFINITY if x.nil? || x == :default y = t.to y = Float::INFINITY if y.nil? || y == :default return x < y ? x <= o && y >= o : y <= o && x >= o end # @api private def instance_of_PStringType(t, o) return false unless o.is_a?(String) # true if size compliant size_t = t.size_type if size_t.nil? || instance_of_PIntegerType(size_t, o.size) values = t.values values.empty? || values.include?(o) else false end end def instance_of_PTupleType(t, o) return false unless o.is_a?(Array) # compute the tuple's min/max size, and check if that size matches size_t = t.size_type || Puppet::Pops::Types::TypeFactory.range(*t.size_range) return false unless instance_of_PIntegerType(size_t, o.size) o.each_with_index do |element, index| return false unless instance_of(t.types[index] || t.types[-1], element) end true end def instance_of_PStructType(t, o) return false unless o.is_a?(Hash) matched = 0 t.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 assignable?(e.key_type, @nil_t) else matched += 1 instance_of(e.value_type, v) end end && matched == o.size end def instance_of_PHashType(t, o) return false unless o.is_a?(Hash) key_t = t.key_type element_t = t.element_type return false unless o.keys.all? {|key| instance_of(key_t, key) } && o.values.all? {|value| instance_of(element_t, value) } size_t = t.size_type || @collection_default_size_t # optimize by calling directly return instance_of_PIntegerType(size_t, o.size) end def instance_of_PDataType(t, o) instance_of(@data_variant_t, o) end def instance_of_PNotUndefType(t, o) !(o.nil? || o == :undef) && (t.type.nil? || instance_of(t.type, o)) end def instance_of_PUndefType(t, o) o.nil? || o == :undef end def instance_of_POptionalType(t, o) instance_of_PUndefType(t, o) || instance_of(t.optional_type, o) end def instance_of_PVariantType(t, o) # instance of variant if o is instance? of any of variant's types t.types.any? { |option_t| instance_of(option_t, o) } end # Answers 'is o an instance of type t' # @api public # def self.instance?(t, o) singleton.instance_of(t,o) end # Answers 'is o an instance of type t' # @api public # def instance?(t, o) instance_of(t,o) end # Answers if t is a puppet type # @api public # def is_ptype?(t) return t.is_a?(Types::PAnyType) end # Answers if t represents the puppet type PUndefType # @api public # def is_pnil?(t) return t.nil? || t.is_a?(Types::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?(Types::PUnitType) return t2 elsif t2.is_a?(Types::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?(Types::PArrayType) && t2.is_a?(Types::PArrayType) type = Types::PArrayType.new() type.element_type = common_type(t1.element_type, t2.element_type) return type end # when both are hashes, return a hash with common key- and element type if t1.is_a?(Types::PHashType) && t2.is_a?(Types::PHashType) type = Types::PHashType.new() type.key_type = common_type(t1.key_type, t2.key_type) type.element_type = common_type(t1.element_type, t2.element_type) return type end # when both are host-classes, reduce to PHostClass[] (since one was not assignable to the other) if t1.is_a?(Types::PHostClassType) && t2.is_a?(Types::PHostClassType) return Types::PHostClassType.new() end # when both are resources, reduce to Resource[T] or Resource[] (since one was not assignable to the other) if t1.is_a?(Types::PResourceType) && t2.is_a?(Types::PResourceType) result = Types::PResourceType.new() # only Resource[] unless the type name is the same if t1.type_name == t2.type_name then result.type_name = t1.type_name end # the cross assignability test above has already determined that they do not have the same type and title return result end # Integers have range, expand the range to the common range if t1.is_a?(Types::PIntegerType) && t2.is_a?(Types::PIntegerType) t1range = from_to_ordered(t1.from, t1.to) t2range = from_to_ordered(t2.from, t2.to) t = Types::PIntegerType.new() from = [t1range[0], t2range[0]].min to = [t1range[1], t2range[1]].max t.from = from unless from == TheInfinity t.to = to unless to == TheInfinity return t end # Floats have range, expand the range to the common range if t1.is_a?(Types::PFloatType) && t2.is_a?(Types::PFloatType) t1range = from_to_ordered(t1.from, t1.to) t2range = from_to_ordered(t2.from, t2.to) t = Types::PFloatType.new() from = [t1range[0], t2range[0]].min to = [t1range[1], t2range[1]].max t.from = from unless from == TheInfinity t.to = to unless to == TheInfinity return t end if t1.is_a?(Types::PStringType) && t2.is_a?(Types::PStringType) t = Types::PStringType.new() t.values = t1.values | t2.values unless t1.values.empty? || t2.values.empty? t.size_type = common_type(t1.size_type, t2.size_type) unless t1.size_type.nil? || t2.size_type.nil? return t end if t1.is_a?(Types::PPatternType) && t2.is_a?(Types::PPatternType) t = Types::PPatternType.new() # must make copies since patterns are contained types, not data-types t.patterns = (t1.patterns | t2.patterns).map(&:copy) return t end if t1.is_a?(Types::PEnumType) && t2.is_a?(Types::PEnumType) # The common type is one that complies with either set t = Types::PEnumType.new t.values = t1.values | t2.values return t end if t1.is_a?(Types::PVariantType) && t2.is_a?(Types::PVariantType) # The common type is one that complies with either set t = Types::PVariantType.new t.types = (t1.types | t2.types).map(&:copy) return t end if t1.is_a?(Types::PRegexpType) && t2.is_a?(Types::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 Types::PPatternType.new() end if t1.is_a?(Types::PCallableType) && t2.is_a?(Types::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 Types::PCallableType.new() end # Common abstract types, from most specific to most general if common_numeric?(t1, t2) return Types::PNumericType.new() end if common_scalar?(t1, t2) return Types::PScalarType.new() end if common_data?(t1,t2) return Types::PDataType.new() end # Meta types Type[Integer] + Type[String] => Type[Data] if t1.is_a?(Types::PType) && t2.is_a?(Types::PType) type = Types::PType.new() type.type = common_type(t1.type, t2.type) return type end # If both are Runtime types if t1.is_a?(Types::PRuntimeType) && t2.is_a?(Types::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 = Types::ClassLoader.provide_from_type(t1) c2 = Types::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 Types::PRuntimeType.new(:runtime => :ruby, :runtime_type_name => c1_super.name) end end end end end # They better both be Any type, or the wrong thing was asked and nil is returned if t1.is_a?(Types::PAnyType) && t2.is_a?(Types::PAnyType) return Types::PAnyType.new() end 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 # Produces a string representing the type # @api public # def string(t) @@string_visitor.visit_this_0(self, t) end # Produces a debug string representing the type (possibly with more information that the regular string format) # @api public # def debug_string(t) @@inspect_visitor.visit_this_0(self, t) 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.collect() {|o| infer(o) }) end # The type of all classes is PType # @api private # def infer_Class(o) Types::PType.new() end # @api private def infer_Closure(o) o.type() end # @api private def infer_Function(o) o.class.dispatcher.to_type end # @api private def infer_Object(o) Types::PRuntimeType.new(:runtime => :ruby, :runtime_type_name => o.class.name) end # The type of all types is PType # @api private # def infer_PAnyType(o) type = Types::PType.new() type.type = o.copy type end # The type of all types is PType # This is the metatype short circuit. # @api private # def infer_PType(o) type = Types::PType.new() type.type = o.copy type end # @api private def infer_String(o) t = Types::PStringType.new() t.addValues(o) t.size_type = size_as_type(o) t end # @api private def infer_Float(o) t = Types::PFloatType.new() t.from = o t.to = o t end # @api private def infer_Integer(o) t = Types::PIntegerType.new() t.from = o t.to = o t end # @api private def infer_Regexp(o) t = Types::PRegexpType.new() t.pattern = o.source t end # @api private def infer_NilClass(o) Types::PUndefType.new() end # @api private # @param o [Proc] def infer_Proc(o) min = 0 max = 0 if o.respond_to?(:parameters) mapped_types = o.parameters.map do |p| param_t = Types::PAnyType.new case p[0] when :rest max = :default break param_t when :req min += 1 end max += 1 param_t end else # Cannot correctly compute the signature in Ruby 1.8.7 because arity for # optional values is screwed up (there is no way to get the upper limit), # an optional looks the same as a varargs. arity = o.arity if arity < 0 min = -arity - 1 max = :default # i.e. infinite (which is wrong when there are optional - flaw in 1.8.7) else min = max = arity end mapped_types = Array.new(min) { Types::PAnyType.new } end if min == 0 || min != max mapped_types << min mapped_types << max end Types::TypeFactory.callable(*mapped_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 Types::PDefaultType.new() else infer_Object(o) end end # @api private def infer_TrueClass(o) Types::PBooleanType.new() end # @api private def infer_FalseClass(o) Types::PBooleanType.new() end # @api private # A Puppet::Parser::Resource, or Puppet::Resource # def infer_Resource(o) t = Types::PResourceType.new() t.type_name = o.type.to_s.downcase # 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 t.title = (:undef == title ? '' : title) type = Types::PType.new() type.type = t type end # @api private def infer_Array(o) type = Types::PArrayType.new() type.element_type = if o.empty? Types::PUndefType.new() else infer_and_reduce_type(o) end type.size_type = size_as_type(o) type end # @api private def infer_Hash(o) type = Types::PHashType.new() if o.empty? ktype = Types::PUndefType.new() etype = Types::PUndefType.new() else ktype = infer_and_reduce_type(o.keys()) etype = infer_and_reduce_type(o.values()) end type.key_type = ktype type.element_type = etype type.size_type = size_as_type(o) type end def size_as_type(collection) size = collection.size t = Types::PIntegerType.new() t.from = size t.to = size t 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? type = Types::PArrayType.new() type.element_type = Types::PUndefType.new() type.size_type = size_as_type(o) else type = Types::PTupleType.new() type.types = o.map() {|x| infer_set(x) } end type end def infer_set_Hash(o) if o.empty? type = Types::PHashType.new type.key_type = Types::PUndefType.new type.element_type = Types::PUndefType.new type.size_type = size_as_type(o) elsif o.keys.all? {|k| instance_of_PStringType(@non_empty_string_t, k) } type = Types::PStructType.new type.elements = o.map do |k,v| element = Types::PStructElement.new element.key_type = infer_String(k) element.value_type = infer_set(v) element end else type = Types::PHashType.new ktype = Types::PVariantType.new ktype.types = o.keys.map {|k| infer_set(k) } etype = Types::PVariantType.new etype.types = o.values.map {|e| infer_set(e) } type.key_type = unwrap_single_variant(ktype) type.element_type = unwrap_single_variant(etype) type.size_type = size_as_type(o) end type end def unwrap_single_variant(possible_variant) if possible_variant.is_a?(Types::PVariantType) && possible_variant.types.size == 1 possible_variant.types[0] else possible_variant end end # False in general type calculator # @api private def assignable_Object(t, t2) false end # @api private def assignable_PAnyType(t, t2) t2.is_a?(Types::PAnyType) end # @api private def assignable_PNotUndefType(t, t2) !assignable?(t2, @nil_t) && (t.type.nil? || assignable?(t.type, t2)) end # @api private def assignable_PUndefType(t, t2) # Only undef/nil is assignable to nil type t2.is_a?(Types::PUndefType) end # Anything is assignable to a Unit type # @api private def assignable_PUnitType(t, t2) true end # @api private def assignable_PDefaultType(t, t2) # Only default is assignable to default type t2.is_a?(Types::PDefaultType) end # @api private def assignable_PScalarType(t, t2) t2.is_a?(Types::PScalarType) end # @api private def assignable_PNumericType(t, t2) t2.is_a?(Types::PNumericType) end # @api private def assignable_PIntegerType(t, t2) return false unless t2.is_a?(Types::PIntegerType) trange = from_to_ordered(t.from, t.to) t2range = from_to_ordered(t2.from, t2.to) # If t2 min and max are within the range of t trange[0] <= t2range[0] && trange[1] >= t2range[1] 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 from_to_ordered(from, to) x = (from.nil? || from == :default) ? -TheInfinity : from y = (to.nil? || to == :default) ? TheInfinity : to if x < y [x, y] else [y, x] end end # @api private def assignable_PVariantType(t, t2) # Data is a specific variant t2 = @data_variant_t if t2.is_a?(Types::PDataType) if t2.is_a?(Types::PVariantType) # A variant is assignable if all of its options are assignable to one of this type's options return true if t == t2 t2.types.all? do |other| # if the other is a Variant, all of its options, but be assignable to one of this type's options other = other.is_a?(Types::PDataType) ? @data_variant_t : other if other.is_a?(Types::PVariantType) assignable?(t, other) else t.types.any? {|option_t| assignable?(option_t, other) } end end else # A variant is assignable if t2 is assignable to any of its types t.types.any? { |option_t| assignable?(option_t, t2) } end end # Catch all not callable combinations def callable_Object(o, callable_t) false end def callable_PTupleType(args_tuple, callable_t) if args_tuple.size_type raise ArgumentError, "Callable tuple may not have a size constraint when used as args" end # Assume no block was given - i.e. it is nil, and its type is PUndefType block_t = @nil_t if self.class.is_kind_of_callable?(args_tuple.types.last) # a split is needed to make it possible to use required, optional, and varargs semantics # of the tuple type. # args_tuple = args_tuple.copy # to drop the callable, it must be removed explicitly since this is an rgen array args_tuple.removeTypes(block_t = args_tuple.types.last()) else # no block was given, if it is required, the below will fail end # unless argument types match parameter types return false unless assignable?(callable_t.param_types, args_tuple) # can the given block be *called* with a signature requirement specified by callable_t? assignable?(callable_t.block_type || @nil_t, block_t) end # @api private def self.is_kind_of_callable?(t, optional = true) case t when Types::PCallableType true when Types::POptionalType optional && is_kind_of_callable?(t.optional_type, optional) when Types::PVariantType t.types.all? {|t2| is_kind_of_callable?(t2, optional) } else false end end # @api private def self.is_kind_of_optional?(t, optional = true) case t when Types::POptionalType true when Types::PVariantType t.types.all? {|t2| is_kind_of_optional?(t2, optional) } else false end end def callable_PArrayType(args_array, callable_t) return false unless assignable?(callable_t.param_types, args_array) # does not support calling with a block, but have to check that callable is ok with missing block assignable?(callable_t.block_type || @nil_t, @nil_t) end def callable_PUndefType(nil_t, callable_t) # if callable_t is Optional (or indeed PUndefType), this means that 'missing callable' is accepted assignable?(callable_t, nil_t) end def callable_PCallableType(given_callable_t, required_callable_t) # If the required callable is euqal or more specific than the given, the given is callable assignable?(required_callable_t, given_callable_t) end def max(a,b) a >=b ? a : b end def min(a,b) a <= b ? a : b end def assignable_PTupleType(t, t2) return true if t == t2 || t.types.empty? && (t2.is_a?(Types::PArrayType)) size_t = t.size_type || Puppet::Pops::Types::TypeFactory.range(*t.size_range) if t2.is_a?(Types::PTupleType) size_t2 = t2.size_type || Puppet::Pops::Types::TypeFactory.range(*t2.size_range) # not assignable if the number of types in t2 is outside number of types in t1 if assignable?(size_t, size_t2) t2.types.size.times do |index| return false unless assignable?((t.types[index] || t.types[-1]), t2.types[index]) end return true else return false end elsif t2.is_a?(Types::PArrayType) t2_entry = t2.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 t2_entry.nil? size_t = t.size_type || Puppet::Pops::Types::TypeFactory.range(*t.size_range) size_t2 = t2.size_type || @collection_default_size_t return false unless assignable?(size_t, size_t2) min(t.types.size, size_t2.range()[1]).times do |index| return false unless assignable?((t.types[index] || t.types[-1]), t2_entry) end true else false end 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 # @api private # def assignable_PStructType(t, t2) if t2.is_a?(Types::PStructType) h2 = t2.hashed_elements matched = 0 t.elements.all? do |e1| e2 = h2[e1.name] if e2.nil? assignable?(e1.key_type, @nil_t) else matched += 1 assignable?(e1.key_type, e2.key_type) && assignable?(e1.value_type, e2.value_type) end end && matched == h2.size elsif t2.is_a?(Types::PHashType) required = 0 required_elements_assignable = t.elements.all? do |e| if assignable?(e.key_type, @nil_t) true else required += 1 assignable?(e.value_type, t2.element_type) end end if required_elements_assignable size_t2 = t2.size_type || @collection_default_size_t size_t = Types::PIntegerType.new size_t.from = required size_t.to = t.elements.size assignable_PIntegerType(size_t, size_t2) end else false end end # @api private def assignable_POptionalType(t, t2) return true if t2.is_a?(Types::PUndefType) return true if t.optional_type.nil? if t2.is_a?(Types::POptionalType) assignable?(t.optional_type, t2.optional_type || @t) else assignable?(t.optional_type, t2) end end # @api private def assignable_PEnumType(t, t2) return true if t == t2 if t.values.empty? return true if t2.is_a?(Types::PStringType) || t2.is_a?(Types::PEnumType) || t2.is_a?(Types::PPatternType) end case t2 when Types::PStringType # if the set of strings are all found in the set of enums !t2.values.empty?() && t2.values.all? { |s| t.values.any? { |e| e == s }} when Types::PVariantType t2.types.all? {|variant_t| assignable_PEnumType(t, variant_t) } when Types::PEnumType # empty means any enum return true if t.values.empty? !t2.values.empty? && t2.values.all? { |s| t.values.any? {|e| e == s }} else false end end # @api private def assignable_PStringType(t, t2) if t.values.empty? # 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 size_t = t.size_type || @collection_default_size_t case t2 when Types::PStringType # true if size compliant size_t2 = t2.size_type || @collection_default_size_t assignable_PIntegerType(size_t, size_t2) when Types::PPatternType # true if size constraint is at least 0 to +Infinity (which is the same as the default) assignable_PIntegerType(size_t, @collection_default_size_t) when Types::PEnumType if t2.values && !t2.values.empty? # true if all enum values are within range min, max = t2.values.map(&:size).minmax trange = from_to_ordered(size_t.from, size_t.to) t2range = [min, max] # If t2 min and max are within the range of t trange[0] <= t2range[0] && trange[1] >= t2range[1] else # enum represents all enums, and thus all strings, a sized constrained string can thus not # be assigned any enum (unless it is max size). assignable_PIntegerType(size_t, @collection_default_size_t) end else # no other type matches string false end elsif t2.is_a?(Types::PStringType) # A specific string acts as a set of strings - must have exactly the same strings # In this case, size does not matter since the definition is very precise anyway Set.new(t.values) == Set.new(t2.values) else # All others are false, since no other type describes the same set of specific strings false end end # @api private def assignable_PPatternType(t, t2) return true if t == t2 case t2 when Types::PStringType, Types::PEnumType values = t2.values when Types::PVariantType return t2.types.all? {|variant_t| assignable_PPatternType(t, variant_t) } when Types::PPatternType return t.patterns.empty? ? true : false else return false end if t2.values.empty? # Strings / 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). return t.patterns.empty? ? true : false end # all strings in String/Enum type must match one of the patterns in Pattern type, # or Pattern represents all Patterns == all Strings regexps = t.patterns.map {|p| p.regexp } regexps.empty? || t2.values.all? { |v| regexps.any? {|re| re.match(v) } } end # @api private def assignable_PFloatType(t, t2) return false unless t2.is_a?(Types::PFloatType) trange = from_to_ordered(t.from, t.to) t2range = from_to_ordered(t2.from, t2.to) # If t2 min and max are within the range of t trange[0] <= t2range[0] && trange[1] >= t2range[1] end # @api private def assignable_PBooleanType(t, t2) t2.is_a?(Types::PBooleanType) end # @api private def assignable_PRegexpType(t, t2) t2.is_a?(Types::PRegexpType) && (t.pattern.nil? || t.pattern == t2.pattern) end # @api private def assignable_PCallableType(t, t2) return false unless t2.is_a?(Types::PCallableType) # nil param_types means, any other Callable is assignable return true if t.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 return false unless assignable?(t2.param_types, t.param_types) # names are ignored, they are just information # Blocks must be compatible this_block_t = t.block_type || @nil_t that_block_t = t2.block_type || @nil_t assignable?(that_block_t, this_block_t) end # @api private def assignable_PCollectionType(t, t2) size_t = t.size_type || @collection_default_size_t case t2 when Types::PCollectionType size_t2 = t2.size_type || @collection_default_size_t assignable_PIntegerType(size_t, size_t2) when Types::PTupleType # compute the tuple's min/max size, and check if that size matches from, to = size_range(t2.size_type) t2s = Types::PIntegerType.new() t2s.from = t2.types.size - 1 + from t2s.to = t2.types.size - 1 + to assignable_PIntegerType(size_t, t2s) when Types::PStructType from = to = t2.elements.size t2s = Types::PIntegerType.new() t2s.from = from t2s.to = to assignable_PIntegerType(size_t, t2s) else false end end # @api private def assignable_PType(t, t2) return false unless t2.is_a?(Types::PType) return true if t.type.nil? # wide enough to handle all types return false if t2.type.nil? # wider than t assignable?(t.type, t2.type) end # Array is assignable if t2 is an Array and t2's element type is assignable, or if t2 is a Tuple # where # @api private def assignable_PArrayType(t, t2) if t2.is_a?(Types::PArrayType) return false unless t.element_type.nil? || assignable?(t.element_type, t2.element_type || @t) assignable_PCollectionType(t, t2) elsif t2.is_a?(Types::PTupleType) return false unless t.element_type.nil? || t2.types.all? {|t2_element| assignable?(t.element_type, t2_element) } t2_regular = t2.types[0..-2] t2_ranged = t2.types[-1] t2_from, t2_to = size_range(t2.size_type) t2_required = t2_regular.size + t2_from t_entry = t.element_type # Tuple of anything can not be assigned (unless array is tuple of anything) - this case # was handled at the top of this method. # return false if t_entry.nil? # array type may be size constrained size_t = t.size_type || @collection_default_size_t min, max = size_t.range # Tuple with fewer min entries can not be assigned return false if t2_required < min # Tuple with more optionally available entries can not be assigned return false if t2_regular.size + t2_to > max # each tuple type must be assignable to the element type t2_required.times do |index| t2_entry = tuple_entry_at(t2, t2_from, t2_to, index) return false unless assignable?(t_entry, t2_entry) end # ... and so must the last, possibly optional (ranged) type return assignable?(t_entry, t2_ranged) else false end end # Hash is assignable if t2 is a Hash and t2's key and element types are assignable # @api private def assignable_PHashType(t, t2) case t2 when Types::PHashType return true if (t.size_type.nil? || t.size_type.from == 0) && t2.is_the_empty_hash? return false unless t.key_type.nil? || assignable?(t.key_type, t2.key_type || @t) return false unless t.element_type.nil? || assignable?(t.element_type, t2.element_type || @t) assignable_PCollectionType(t, t2) when Types::PStructType # hash must accept String as key type # hash must accept all value types # hash must accept the size of the struct size_t = t.size_type || @collection_default_size_t min, max = size_t.range struct_size = t2.elements.size key_type = t.key_type element_type = t.element_type ( struct_size >= min && struct_size <= max && t2.elements.all? {|e| (key_type.nil? || instance_of(key_type, e.name)) && (element_type.nil? || assignable?(element_type, e.value_type)) }) else false end end # @api private def assignable_PCatalogEntryType(t1, t2) t2.is_a?(Types::PCatalogEntryType) end # @api private def assignable_PHostClassType(t1, t2) return false unless t2.is_a?(Types::PHostClassType) # Class = Class[name}, Class[name] != Class return true if t1.class_name.nil? # Class[name] = Class[name] return t1.class_name == t2.class_name end # @api private def assignable_PResourceType(t1, t2) return false unless t2.is_a?(Types::PResourceType) return true if t1.type_name.nil? return false if t1.type_name != t2.type_name return true if t1.title.nil? return t1.title == t2.title end # Data is assignable by other Data and by Array[Data] and Hash[Scalar, Data] # @api private def assignable_PDataType(t, t2) # We cannot put the NotUndefType[Data] in the @data_variant_t since that causes an endless recursion case t2 when Types::PDataType true when Types::PNotUndefType assignable?(t, t2.type || @t) else assignable?(@data_variant_t, t2) end end # Assignable if t2'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_PRuntimeType(t1, t2) return false unless t2.is_a?(Types::PRuntimeType) return false unless t1.runtime == t2.runtime return true if t1.runtime_type_name.nil? # t1 is wider return false if t2.runtime_type_name.nil? # t1 not nil, so t2 can not be wider # NOTE: This only supports Ruby, must change when/if the set of runtimes is expanded c1 = class_from_string(t1.runtime_type_name) c2 = class_from_string(t2.runtime_type_name) return false unless c1.is_a?(Class) && c2.is_a?(Class) !!(c2 <= c1) end # @api private def debug_string_Object(t) string(t) end # @api private def string_PType(t) if t.type.nil? "Type" else "Type[#{string(t.type)}]" end end # @api private def string_NilClass(t) ; '?' ; end # @api private def string_String(t) ; t ; end # @api private def string_Symbol(t) ; t.to_s ; end def string_PAnyType(t) ; "Any" ; end # @api private def string_PUndefType(t) ; 'Undef' ; end # @api private def string_PDefaultType(t) ; 'Default' ; end # @api private def string_PBooleanType(t) ; "Boolean" ; end # @api private def string_PScalarType(t) ; "Scalar" ; end # @api private def string_PDataType(t) ; "Data" ; end # @api private def string_PNumericType(t) ; "Numeric" ; end # @api private def string_PIntegerType(t) range = range_array_part(t) unless range.empty? "Integer[#{range.join(', ')}]" else "Integer" end end # Produces a string from an Integer range type that is used inside other type strings # @api private def range_array_part(t) return [] if t.nil? || (t.from.nil? && t.to.nil?) [t.from.nil? ? 'default' : t.from , t.to.nil? ? 'default' : t.to ] end # @api private def string_PFloatType(t) range = range_array_part(t) unless range.empty? "Float[#{range.join(', ')}]" else "Float" end end # @api private def string_PRegexpType(t) t.pattern.nil? ? "Regexp" : "Regexp[#{t.regexp.inspect}]" end # @api private def string_PStringType(t) # skip values in regular output - see debug_string range = range_array_part(t.size_type) unless range.empty? "String[#{range.join(', ')}]" else "String" end end # @api private def debug_string_PStringType(t) range = range_array_part(t.size_type) range_part = range.empty? ? '' : '[' << range.join(' ,') << '], ' "String[" << range_part << (t.values.map {|s| "'#{s}'" }).join(', ') << ']' end # @api private def string_PEnumType(t) return "Enum" if t.values.empty? "Enum[" << t.values.map {|s| "'#{s}'" }.join(', ') << ']' end # @api private def string_PVariantType(t) return "Variant" if t.types.empty? "Variant[" << t.types.map {|t2| string(t2) }.join(', ') << ']' end # @api private def string_PTupleType(t) range = range_array_part(t.size_type) return "Tuple" if t.types.empty? s = "Tuple[" << t.types.map {|t2| string(t2) }.join(', ') unless range.empty? s << ", " << range.join(', ') end s << "]" s end # @api private def string_PCallableType(t) # generic return "Callable" if t.param_types.nil? if t.param_types.types.empty? range = [0, 0] else range = range_array_part(t.param_types.size_type) end # translate to string, and skip Unit types types = t.param_types.types.map {|t2| string(t2) unless t2.class == Types::PUnitType }.compact s = "Callable[" << types.join(', ') unless range.empty? (s << ', ') unless types.empty? s << range.join(', ') end # Add block T last (after min, max) if present) # unless t.block_type.nil? (s << ', ') unless types.empty? && range.empty? s << string(t.block_type) end s << "]" s end # @api private def string_PStructType(t) return "Struct" if t.elements.empty? "Struct[{" << t.elements.map {|element| string(element) }.join(', ') << "}]" end def string_PStructElement(t) k = t.key_type value_optional = assignable?(t.value_type, @nil_t) key_string = if k.is_a?(Types::POptionalType) # Output as literal String value_optional ? "'#{t.name}'" : string(k) else value_optional ? "NotUndef['#{t.name}']" : "'#{t.name}'" end "#{key_string}=>#{string(t.value_type)}" end # @api private def string_PPatternType(t) return "Pattern" if t.patterns.empty? "Pattern[" << t.patterns.map {|s| "#{s.regexp.inspect}" }.join(', ') << ']' end # @api private def string_PCollectionType(t) range = range_array_part(t.size_type) unless range.empty? "Collection[#{range.join(', ')}]" else "Collection" end end # @api private def string_PUnitType(t) "Unit" end # @api private def string_PRuntimeType(t) ; "Runtime[#{string(t.runtime)}, #{string(t.runtime_type_name)}]" ; end # @api private def string_PArrayType(t) parts = [string(t.element_type)] + range_array_part(t.size_type) "Array[#{parts.join(', ')}]" end # @api private def string_PHashType(t) parts = [string(t.key_type), string(t.element_type)] + range_array_part(t.size_type) "Hash[#{parts.join(', ')}]" end # @api private def string_PCatalogEntryType(t) "CatalogEntry" end # @api private def string_PHostClassType(t) if t.class_name "Class[#{t.class_name}]" else "Class" end end # @api private def string_PResourceType(t) if t.type_name if t.title "#{capitalize_segments(t.type_name)}['#{t.title}']" else capitalize_segments(t.type_name) end else "Resource" end end # @api private def string_PNotUndefType(t) contained_type = t.type if contained_type.nil? || contained_type.class == Puppet::Pops::Types::PAnyType 'NotUndef' else if contained_type.is_a?(Puppet::Pops::Types::PStringType) && contained_type.values.size == 1 "NotUndef['#{contained_type.values[0]}']" else "NotUndef[#{string(contained_type)}]" end end end def string_POptionalType(t) optional_type = t.optional_type if optional_type.nil? "Optional" else if optional_type.is_a?(Puppet::Pops::Types::PStringType) && optional_type.values.size == 1 "Optional['#{optional_type.values[0]}']" else "Optional[#{string(optional_type)}]" end end end # Catches all non enumerable types # @api private def enumerable_Object(o) nil end # @api private def enumerable_PIntegerType(t) # Not enumerable if representing an infinite range return nil if t.size == TheInfinity t end def self.copy_as_tuple(t) case t when Types::PTupleType t.copy when Types::PArrayType # transform array to tuple result = Types::PTupleType.new result.addTypes(t.element_type.copy) result.size_type = t.size_type.nil? ? nil : t.size_type.copy result else raise ArgumentError, "Internal Error: Only Array and Tuple can be given to copy_as_tuple" end end private NAME_SEGMENT_SEPARATOR = '::'.freeze def capitalize_segments(s) s.split(NAME_SEGMENT_SEPARATOR).map(&:capitalize).join(NAME_SEGMENT_SEPARATOR) end def class_from_string(str) begin str.split(NAME_SEGMENT_SEPARATOR).inject(Object) do |memo, name_segment| memo.const_get(name_segment) end rescue NameError return nil end end def common_data?(t1, t2) assignable?(@data_t, t1) && assignable?(@data_t, t2) end def common_scalar?(t1, t2) assignable?(@scalar_t, t1) && assignable?(@scalar_t, t2) end def common_numeric?(t1, t2) assignable?(@numeric_t, t1) && assignable?(@numeric_t, t2) end end �����������������������puppet-3.8.5/lib/puppet/pops/types/type_factory.rb��������������������������������������������������0000664�0052762�0001160�00000034631�12650174557�022226� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Helper module that makes creation of type objects simpler. # @api public # module Puppet::Pops::Types::TypeFactory Types = Puppet::Pops::Types @type_calculator = Types::TypeCalculator.singleton @undef_t = Types::PUndefType.new # Produces the Integer type # @api public # def self.integer() Types::PIntegerType.new() end # Produces an Integer range type # @api public # def self.range(from, to) # NOTE! Do not merge the following line to 4.x. It has the same check in the initialize method raise ArgumentError, "'from' must be less or equal to 'to'. Got (#{from}, #{to}" if from.is_a?(Numeric) && to.is_a?(Numeric) && from > to t = Types::PIntegerType.new() # optimize eq with symbol (faster when it is left) t.from = from unless (:default == from || from == 'default') t.to = to unless (:default == to || to == 'default') t end # Produces a Float range type # @api public # def self.float_range(from, to) # NOTE! Do not merge the following line to 4.x. It has the same check in the initialize method raise ArgumentError, "'from' must be less or equal to 'to'. Got (#{from}, #{to}" if from.is_a?(Numeric) && to.is_a?(Numeric) && from > to t = Types::PFloatType.new() # optimize eq with symbol (faster when it is left) t.from = Float(from) unless :default == from || from.nil? t.to = Float(to) unless :default == to || to.nil? t end # Produces the Float type # @api public # def self.float() Types::PFloatType.new() end # Produces the Numeric type # @api public # def self.numeric() Types::PNumericType.new() end # Produces a string representation of the type # @api public # def self.label(t) @type_calculator.string(t) end # Produces the String type, optionally with specific string values # @api public # def self.string(*values) t = Types::PStringType.new() values.each {|v| t.addValues(v) } t 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) t = Types::POptionalType.new t.optional_type = optional_type.is_a?(String) ? string(optional_type) : type_of(optional_type) t end # Produces the Enum type, optionally with specific string values # @api public # def self.enum(*values) t = Types::PEnumType.new() values.each {|v| t.addValues(v) } t end # Produces the Variant type, optionally with the "one of" types # @api public # def self.variant(*types) t = Types::PVariantType.new() types.each {|v| t.addTypes(type_of(v)) } t 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 [Hash<Object, Object>] key => value hash # @return [PStructType] the created Struct type # def self.struct(hash = {}) tc = @type_calculator t = Types::PStructType.new t.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?(Types::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, @undef_t) else # assert that the key type is one of String[1], NotUndef[String[1]] and Optional[String[1]] case key_type when Types::PNotUndefType # We can loose the NotUndef wrapper here since String[1] isn't optional anyway key_type = key_type.type s = key_type when Types::POptionalType s = key_type.optional_type else s = key_type end unless (s.is_a?(Puppet::Pops::Types::PStringType) || s.is_a?(Puppet::Pops::Types::PEnumType)) && s.values.size == 1 && !s.values[0].empty? raise ArgumentError, 'Unable to extract a non-empty literal string from Struct member key type' if key_type.empty? end end elem = Types::PStructElement.new elem.key_type = key_type elem.value_type = value_type elem end t end def self.tuple(*types) t = Types::PTupleType.new types.each {|elem| t.addTypes(type_of(elem)) } t end # Produces the Boolean type # @api public # def self.boolean() Types::PBooleanType.new() end # Produces the Any type # @api public # def self.any() Types::PAnyType.new() 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) t = Types::PRegexpType.new() if pattern t.pattern = pattern.is_a?(Regexp) ? pattern.inspect[1..-2] : pattern end t.regexp() unless pattern.nil? # compile pattern to catch errors t end def self.pattern(*regular_expressions) t = Types::PPatternType.new() regular_expressions.each do |re| case re when String re_T = Types::PRegexpType.new() re_T.pattern = re re_T.regexp() # compile it to catch errors t.addPatterns(re_T) when Regexp re_T = Types::PRegexpType.new() # Regep.to_s includes options user did not enter and does not escape source # to work either as a string or as a // regexp. The inspect method does a better # job, but includes the // re_T.pattern = re.inspect[1..-2] t.addPatterns(re_T) when Types::PRegexpType t.addPatterns(re.copy) when Types::PPatternType re.patterns.each do |p| t.addPatterns(p.copy) end else raise ArgumentError, "Only String, Regexp, Pattern-Type, and Regexp-Type are allowed: got '#{re.class}" end end t end # Produces the Literal type # @api public # def self.scalar() Types::PScalarType.new() end # Produces a CallableType matching all callables # @api public # def self.all_callables() return Puppet::Pops::Types::PCallableType.new 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 Puppet::Pops::Types::TypeCalculator.is_kind_of_callable?(params.last) last_callable = true end 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] 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 << Types::PUnitType.new end # create a signature callable_t = Types::PCallableType.new() tuple_t = tuple(*types) tuple_t.size_type = size_type unless size_type.nil? callable_t.param_types = tuple_t callable_t.block_type = block_t callable_t end def self.with_block(callable, *block_params) callable.block_type = callable(*block_params) callable end def self.with_optional_block(callable, *block_params) callable.block_type = optional(callable(*block_params)) callable end # Produces the abstract type Collection # @api public # def self.collection() Types::PCollectionType.new() end # Produces the Data type # @api public # def self.data() Types::PDataType.new() end # Creates an instance of the Undef type # @api public def self.undef() Types::PUndefType.new() end # Creates an instance of the Default type # @api public def self.default() Types::PDefaultType.new() end # Produces an instance of the abstract type PCatalogEntryType def self.catalog_entry() Types::PCatalogEntryType.new() 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) type = Types::PResourceType.new() type_name = type_name.type_name if type_name.is_a?(Types::PResourceType) type_name = type_name.downcase unless type_name.nil? type.type_name = type_name unless type_name.nil? || type_name =~ Puppet::Pops::Patterns::CLASSREF raise ArgumentError, "Illegal type name '#{type.type_name}'" end if type_name.nil? && !title.nil? raise ArgumentError, "The type name cannot be nil, if title is given" end type.title = title type end # Produces PHostClassType with a string class_name. A PHostClassType with # nil or empty name is compatible with any other PHostClassType. A # PHostClassType with a given name is only compatible with a PHostClassType # with the same name. # def self.host_class(class_name = nil) type = Types::PHostClassType.new() unless class_name.nil? type.class_name = class_name.sub(/^::/, '') end type 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) type = Types::PArrayType.new() type.element_type = type_of(o) 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()) type = Types::PHashType.new() type.key_type = type_of(key) type.element_type = type_of(value) type end # Produces a type for Array[Data] # @api public # def self.array_of_data() type = Types::PArrayType.new() type.element_type = data() type end # Produces a type for Hash[Scalar, Data] # @api public # def self.hash_of_data() type = Types::PHashType.new() type.key_type = scalar() type.element_type = data() type 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 [Puppet::Pops::Types::PNotUndefType] the NotUndef type # # @api public # def self.not_undef(inst_type = nil) type = Types::PNotUndefType.new() inst_type = string(inst_type) if inst_type.is_a?(String) type.type = inst_type type end # Produces a type for Type[T] # @api public # def self.type_type(inst_type = nil) type = Types::PType.new() type.type = inst_type type 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?(Types::PAnyType) o elsif o.is_a?(String) Types::PRuntimeType.new(:runtime => :ruby, :runtime_type_name => 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 Types::PRuntimeType.new(:runtime => :ruby, :runtime_type_name => 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) Types::PRuntimeType.new(:runtime => :ruby, :runtime_type_name => 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) Types::PRuntimeType.new(:runtime => runtime, :runtime_type_name => runtime_type_name) end # Sets the accepted size range of a collection if something other than the # default 0 to Infinity is wanted. The semantics for from/to are the same as # for #range # def self.constrain_size(collection_t, from, to) collection_t.size_type = range(from, to) collection_t 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 �������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/types/type_parser.rb���������������������������������������������������0000664�0052762�0001160�00000032134�12650174557�022047� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This class provides parsing of Type Specification from a string into the Type # Model that is produced by the Puppet::Pops::Types::TypeFactory. # # The Type Specifications that are parsed are the same as the stringified forms # of types produced by the {Puppet::Pops::Types::TypeCalculator TypeCalculator}. # # @api public class Puppet::Pops::Types::TypeParser # @api private TYPES = Puppet::Pops::Types::TypeFactory # @api public def initialize @parser = Puppet::Pops::Parser::Parser.new() @type_transformer = Puppet::Pops::Visitor.new(nil, "interpret", 0, 0) @undef_t = TYPES.undef 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 # {Puppet::Pops::Types::TypeCalculator#string TypeCalculator#string} method. # @return [Puppet::Pops::Types::PAnyType] a specialization of the PAnyType representing the type. # # @api public # def parse(string) # TODO: This state (@string) can be removed since the parse result of newer future parser # contains a Locator in its SourcePosAdapter and the Locator keeps the string. # This way, there is no difference between a parsed "string" and something that has been parsed # earlier and fed to 'interpret' # @string = string model = @parser.parse_string(@string) if model interpret(model.current) else raise_invalid_type_specification_error end end # @api private def interpret(ast) result = @type_transformer.visit_this_0(self, ast) result = result.body if result.is_a?(Puppet::Pops::Model::Program) raise_invalid_type_specification_error unless result.is_a?(Puppet::Pops::Types::PAnyType) result end # @api private def interpret_any(ast) @type_transformer.visit_this_0(self, ast) end # @api private def interpret_Object(o) raise_invalid_type_specification_error end # @api private def interpret_Program(o) interpret(o.body) end # @api private def interpret_QualifiedName(o) o.value end # @api private def interpret_LiteralString(o) o.value end def interpret_LiteralRegularExpression(o) o.value end # @api private def interpret_String(o) o end # @api private def interpret_LiteralDefault(o) :default end # @api private def interpret_LiteralInteger(o) o.value end # @api private def interpret_LiteralFloat(o) o.value end # @api private def interpret_LiteralHash(o) result = {} o.entries.each do |entry| result[@type_transformer.visit_this_0(self, entry.key)] = @type_transformer.visit_this_0(self, entry.value) end result end # @api private def interpret_QualifiedReference(name_ast) case name_ast.value when "integer" TYPES.integer when "float" TYPES.float when "numeric" TYPES.numeric when "string" TYPES.string when "enum" TYPES.enum when "boolean" TYPES.boolean when "pattern" TYPES.pattern when "regexp" TYPES.regexp when "data" TYPES.data when "array" TYPES.array_of_data when "hash" TYPES.hash_of_data when "class" TYPES.host_class() when "resource" TYPES.resource() when "collection" TYPES.collection() when "scalar" TYPES.scalar() when "catalogentry" TYPES.catalog_entry() when "undef" TYPES.undef() when "notundef" TYPES.not_undef() when "default" TYPES.default() when "any" TYPES.any() when "variant" TYPES.variant() when "optional" TYPES.optional() when "runtime" TYPES.runtime() when "type" TYPES.type_type() when "tuple" TYPES.tuple() when "struct" TYPES.struct() when "callable" # A generic callable as opposed to one that does not accept arguments TYPES.all_callables() else TYPES.resource(name_ast.value) end end # @api private def interpret_AccessExpression(parameterized_ast) parameters = parameterized_ast.keys.collect { |param| interpret_any(param) } unless parameterized_ast.left_expr.is_a?(Puppet::Pops::Model::QualifiedReference) raise_invalid_type_specification_error end case parameterized_ast.left_expr.value when "array" case parameters.size when 1 when 2 size_type = if parameters[1].is_a?(Puppet::Pops::Types::PIntegerType) parameters[1].copy else assert_range_parameter(parameters[1]) TYPES.range(parameters[1], :default) end when 3 assert_range_parameter(parameters[1]) assert_range_parameter(parameters[2]) size_type = TYPES.range(parameters[1], parameters[2]) else raise_invalid_parameters_error("Array", "1 to 3", parameters.size) end assert_type(parameters[0]) t = TYPES.array_of(parameters[0]) t.size_type = size_type if size_type t when "hash" result = case parameters.size when 2 assert_type(parameters[0]) assert_type(parameters[1]) TYPES.hash_of(parameters[1], parameters[0]) when 3 size_type = if parameters[2].is_a?(Puppet::Pops::Types::PIntegerType) parameters[2].copy else assert_range_parameter(parameters[2]) TYPES.range(parameters[2], :default) end assert_type(parameters[0]) assert_type(parameters[1]) TYPES.hash_of(parameters[1], parameters[0]) when 4 assert_range_parameter(parameters[2]) assert_range_parameter(parameters[3]) size_type = TYPES.range(parameters[2], parameters[3]) assert_type(parameters[0]) assert_type(parameters[1]) TYPES.hash_of(parameters[1], parameters[0]) else raise_invalid_parameters_error("Hash", "2 to 4", parameters.size) end result.size_type = size_type if size_type result when "collection" size_type = case parameters.size when 1 if parameters[0].is_a?(Puppet::Pops::Types::PIntegerType) parameters[0].copy else assert_range_parameter(parameters[0]) TYPES.range(parameters[0], :default) end when 2 assert_range_parameter(parameters[0]) assert_range_parameter(parameters[1]) TYPES.range(parameters[0], parameters[1]) else raise_invalid_parameters_error("Collection", "1 to 2", parameters.size) end result = TYPES.collection result.size_type = size_type result when "class" if parameters.size != 1 raise_invalid_parameters_error("Class", 1, parameters.size) end TYPES.host_class(parameters[0]) when "resource" if parameters.size == 1 TYPES.resource(parameters[0]) elsif parameters.size != 2 raise_invalid_parameters_error("Resource", "1 or 2", parameters.size) else TYPES.resource(parameters[0], parameters[1]) end when "regexp" # 1 parameter being a string, or regular expression raise_invalid_parameters_error("Regexp", "1", parameters.size) unless parameters.size == 1 TYPES.regexp(parameters[0]) when "enum" # 1..m parameters being strings raise_invalid_parameters_error("Enum", "1 or more", parameters.size) unless parameters.size >= 1 TYPES.enum(*parameters) when "pattern" # 1..m parameters being strings or regular expressions raise_invalid_parameters_error("Pattern", "1 or more", parameters.size) unless parameters.size >= 1 TYPES.pattern(*parameters) when "variant" # 1..m parameters being strings or regular expressions raise_invalid_parameters_error("Variant", "1 or more", parameters.size) unless parameters.size >= 1 TYPES.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 if TYPES.is_range_parameter?(parameters[-2]) # min, max specification min = parameters[-2] min = (min == :default || min == 'default') ? 0 : min assert_range_parameter(parameters[-1]) max = parameters[-1] max = max == :default ? nil : max parameters = parameters[0, length-2] elsif TYPES.is_range_parameter?(parameters[-1]) min = parameters[-1] min = (min == :default || min == 'default') ? 0 : min max = nil parameters = parameters[0, length-1] end t = TYPES.tuple(*parameters) if min || max TYPES.constrain_size(t, min, max) end t when "callable" # 1..m parameters being types (last three optionally integer or literal default, and a callable) TYPES.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 unless h.is_a?(Hash) TYPES.struct(h) when "integer" if parameters.size == 1 case parameters[0] when Integer TYPES.range(parameters[0], :default) when :default TYPES.integer # unbound end elsif parameters.size != 2 raise_invalid_parameters_error("Integer", "1 or 2", parameters.size) else TYPES.range(parameters[0] == :default ? nil : parameters[0], parameters[1] == :default ? nil : parameters[1]) end when "float" if parameters.size == 1 case parameters[0] when Integer, Float TYPES.float_range(parameters[0], :default) when :default TYPES.float # unbound end elsif parameters.size != 2 raise_invalid_parameters_error("Float", "1 or 2", parameters.size) else TYPES.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?(Puppet::Pops::Types::PIntegerType) parameters[0].copy else assert_range_parameter(parameters[0]) TYPES.range(parameters[0], :default) end when 2 assert_range_parameter(parameters[0]) assert_range_parameter(parameters[1]) TYPES.range(parameters[0], parameters[1]) else raise_invalid_parameters_error("String", "1 to 2", parameters.size) end result = TYPES.string result.size_type = size_type result when "optional" if parameters.size != 1 raise_invalid_parameters_error("Optional", 1, parameters.size) end param = parameters[0] assert_type(param) unless param.is_a?(String) TYPES.optional(param) when "any", "data", "catalogentry", "boolean", "scalar", "undef", "numeric", "default" raise_unparameterized_type_error(parameterized_ast.left_expr) when "notundef" case parameters.size when 0 TYPES.not_undef when 1 param = parameters[0] assert_type(param) unless param.is_a?(String) TYPES.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(parameters[0]) TYPES.type_type(parameters[0]) when "runtime" raise_invalid_parameters_error("Runtime", "2", parameters.size) unless parameters.size == 2 TYPES.runtime(*parameters) else # It is a resource such a File['/tmp/foo'] type_name = parameterized_ast.left_expr.value if parameters.size != 1 raise_invalid_parameters_error(type_name.capitalize, 1, parameters.size) end TYPES.resource(type_name, parameters[0]) end end private def assert_type(t) raise_invalid_type_specification_error unless t.is_a?(Puppet::Pops::Types::PAnyType) true end def assert_range_parameter(t) raise_invalid_type_specification_error unless TYPES.is_range_parameter?(t) end def raise_invalid_type_specification_error raise Puppet::ParseError, "The expression <#{@string}> is not a valid type specification." end def raise_invalid_parameters_error(type, required, given) raise Puppet::ParseError, "Invalid number of type parameters specified: #{type} requires #{required}, #{given} provided" end def raise_unparameterized_type_error(ast) raise Puppet::ParseError, "Not a parameterized type <#{original_text_of(ast)}>" end def raise_unknown_type_error(ast) raise Puppet::ParseError, "Unknown type <#{original_text_of(ast)}>" end def original_text_of(ast) position = Puppet::Pops::Adapters::SourcePosAdapter.adapt(ast) position.extract_text() end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/types/types.rb���������������������������������������������������������0000664�0052762�0001160�00000023331�12650174557�020655� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'rgen/metamodel_builder' # The Types model is a model of Puppet Language types. # It consists of two parts; the meta-model expressed using RGen (in types_meta.rb) and this file which # mixes in the implementation. # # @api public # module Puppet::Pops require 'puppet/pops/types/types_meta' # TODO: See PUP-2978 for possible performance optimization # Mix in implementation part of the Bindings Module module Types # Used as end in a range INFINITY = 1.0 / 0.0 NEGATIVE_INFINITY = -INFINITY class TypeModelObject < RGen::MetamodelBuilder::MMBase include Puppet::Pops::Visitable include Puppet::Pops::Adaptable include Puppet::Pops::Containment end class PAnyType < TypeModelObject module ClassModule # Produce a deep copy of the type def copy Marshal.load(Marshal.dump(self)) end def hash self.class.hash end def ==(o) self.class == o.class end alias eql? == def to_s Puppet::Pops::Types::TypeCalculator.string(self) end end end class PNotUndefType < PAnyType module ClassModule def hash [self.class, type].hash end def ==(o) self.class == o.class && type == o.type end end end class PType < PAnyType module ClassModule def hash [self.class, type].hash end def ==(o) self.class == o.class && type == o.type end end end class PDataType < PAnyType module ClassModule def ==(o) self.class == o.class || o.class == PVariantType && o == Puppet::Pops::Types::TypeCalculator.data_variant() end end end class PVariantType < PAnyType module ClassModule def hash [self.class, Set.new(self.types)].hash end def ==(o) (self.class == o.class && Set.new(types) == Set.new(o.types)) || (o.class == PDataType && self == Puppet::Pops::Types::TypeCalculator.data_variant()) end end end class PEnumType < PScalarType module ClassModule def hash [self.class, Set.new(self.values)].hash end def ==(o) self.class == o.class && Set.new(values) == Set.new(o.values) end end end class PIntegerType < PNumericType module ClassModule # The integer type is enumerable when it defines a range include Enumerable # Returns Float.Infinity if one end of the range is unbound def size return INFINITY if from.nil? || to.nil? 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 f = from || NEGATIVE_INFINITY t = to || INFINITY [f, t] end # Returns Enumerator if no block is given # Returns self if size is infinity (does not yield) def each return self.to_enum unless block_given? return nil if from.nil? || to.nil? from.upto(to) {|x| yield x } end def hash [self.class, from, to].hash end def ==(o) self.class == o.class && from == o.from && to == o.to end end end class PFloatType < PNumericType module ClassModule def hash [self.class, from, to].hash end def ==(o) self.class == o.class && from == o.from && to == o.to end end end class PStringType < PScalarType module ClassModule def hash [self.class, self.size_type, Set.new(self.values)].hash end def ==(o) self.class == o.class && self.size_type == o.size_type && Set.new(values) == Set.new(o.values) end end end class PRegexpType < PScalarType module ClassModule def regexp_derived @_regexp = Regexp.new(pattern) unless @_regexp && @_regexp.source == pattern @_regexp end def hash [self.class, pattern].hash end def ==(o) self.class == o.class && pattern == o.pattern end end end class PPatternType < PScalarType module ClassModule def hash [self.class, Set.new(patterns)].hash end def ==(o) self.class == o.class && Set.new(patterns) == Set.new(o.patterns) end end end class PCollectionType < PAnyType module ClassModule # Returns an array with from (min) size to (max) size def size_range return [0, INFINITY] if size_type.nil? f = size_type.from || 0 t = size_type.to || INFINITY [f, t] end def hash [self.class, element_type, size_type].hash end def ==(o) self.class == o.class && element_type == o.element_type && size_type == o.size_type end end end class PStructElement < TypeModelObject module ClassModule def hash [self.class, value_type, key_type].hash end def name k = key_type k = k.optional_type if k.is_a?(POptionalType) k.values[0] end def ==(o) self.class == o.class && value_type == o.value_type && key_type == o.key_type end end end class PStructType < PAnyType module ClassModule def hashed_elements_derived @_hashed ||= elements.reduce({}) {|memo, e| memo[e.name] = e; memo } @_hashed end def clear_hashed_elements @_hashed = nil end def hash [self.class, Set.new(elements)].hash end def ==(o) self.class == o.class && hashed_elements == o.hashed_elements end end end class PTupleType < PAnyType module ClassModule # Returns the number of elements accepted [min, max] in the tuple def size_range types_size = types.size size_type.nil? ? [types_size, types_size] : size_type.range 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 types_size = types.size if size_type.nil? return [1, 1] end 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 [self.class, size_type, Set.new(types)].hash end def ==(o) self.class == o.class && types == o.types && size_type == o.size_type end end end class PCallableType < PAnyType module ClassModule # Returns the number of accepted arguments [min, max] def size_range param_types.size_range end # Returns the number of accepted arguments for the last parameter type [min, max] # def last_range param_types.repeat_last_range end # Range [0,0], [0,1], or [1,1] for the block # def block_range case block_type when Puppet::Pops::Types::POptionalType [0,1] when Puppet::Pops::Types::PVariantType, Puppet::Pops::Types::PCallableType [1,1] else [0,0] end end def hash [self.class, Set.new(param_types), block_type].hash end def ==(o) self.class == o.class && args_type == o.args_type && block_type == o.block_type end end end class PArrayType < PCollectionType module ClassModule def hash [self.class, self.element_type, self.size_type].hash end def ==(o) self.class == o.class && self.element_type == o.element_type && self.size_type == o.size_type end end end class PHashType < PCollectionType module ClassModule def hash [self.class, key_type, self.element_type, self.size_type].hash end def ==(o) self.class == o.class && key_type == o.key_type && self.element_type == o.element_type && self.size_type == o.size_type end def is_the_empty_hash? size_type.is_a?(PIntegerType) && size_type.from == 0 && size_type.to == 0 && key_type.is_a?(PUndefType) && element_type.is_a?(PUndefType) end end end class PRuntimeType < PAnyType module ClassModule def hash [self.class, runtime, runtime_type_name].hash end def ==(o) self.class == o.class && runtime == o.runtime && runtime_type_name == o.runtime_type_name end end end class PHostClassType < PCatalogEntryType module ClassModule def hash [self.class, class_name].hash end def ==(o) self.class == o.class && class_name == o.class_name end end end class PResourceType < PCatalogEntryType module ClassModule def hash [self.class, type_name, title].hash end def ==(o) self.class == o.class && type_name == o.type_name && title == o.title end end end class POptionalType < PAnyType module ClassModule def hash [self.class, optional_type].hash end def ==(o) self.class == o.class && optional_type == o.optional_type end end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/types/types_meta.rb����������������������������������������������������0000664�0052762�0001160�00000014017�12650174557�021664� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'rgen/metamodel_builder' # The Types model is a model of Puppet Language types. # # The exact relationship between types is not visible in this model wrt. the PDataType which is an abstraction # of Scalar, Array[Data], and Hash[Scalar, Data] nested to any depth. This means it is not possible to # infer the type by simply looking at the inheritance hierarchy. The {Puppet::Pops::Types::TypeCalculator} should # be used to answer questions about types. The {Puppet::Pops::Types::TypeFactory} 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 # module Puppet::Pops::Types extend RGen::MetamodelBuilder::ModuleExtension class TypeModelObject < RGen::MetamodelBuilder::MMBase abstract end # Base type for all types except {Puppet::Pops::Types::PType PType}, the type of types. # @api public # class PAnyType < TypeModelObject end # A type that is assignable from the same types as its contained `type` except the # types assignable from {Puppet::Pops::Types::PUndefType} # # @api public # class PNotUndefType < PAnyType contains_one_uni 'type', PAnyType end # The type of types. # @api public # class PType < PAnyType contains_one_uni 'type', PAnyType end # @api public # class PUndefType < PAnyType end # A type private to the type system that describes "ignored type" - i.e. "I am what you are" # @api private # class PUnitType < PAnyType end # @api public # class PDefaultType < PAnyType end # A flexible data type, being assignable to its subtypes as well as PArrayType and PHashType with element type assignable to PDataType. # # @api public # class PDataType < PAnyType end # A flexible type describing an any? of other types # @api public # class PVariantType < PAnyType contains_many_uni 'types', PAnyType, :lowerBound => 1 end # Type that is PDataType compatible, but is not a PCollectionType. # @api public # class PScalarType < PAnyType end # A string type describing the set of strings having one of the given values # @api public # class PEnumType < PScalarType has_many_attr 'values', String, :lowerBound => 1 end # @api public # class PNumericType < PScalarType end # @api public # class PIntegerType < PNumericType has_attr 'from', Integer, :lowerBound => 0 has_attr 'to', Integer, :lowerBound => 0 end # @api public # class PFloatType < PNumericType has_attr 'from', Float, :lowerBound => 0 has_attr 'to', Float, :lowerBound => 0 end # @api public # class PStringType < PScalarType has_many_attr 'values', String, :lowerBound => 0, :upperBound => -1, :unique => true contains_one_uni 'size_type', PIntegerType end # @api public # class PRegexpType < PScalarType has_attr 'pattern', String, :lowerBound => 1 has_attr 'regexp', Object, :derived => true 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 < PScalarType contains_many_uni 'patterns', PRegexpType end # @api public # class PBooleanType < PScalarType end # @api public # class PCollectionType < PAnyType contains_one_uni 'element_type', PAnyType contains_one_uni 'size_type', PIntegerType end # @api public # class PStructElement < TypeModelObject # key_type must be either String[1] or Optional[String[1]] and the String[1] must # have a values collection with exactly one element contains_one_uni 'key_type', PAnyType, :lowerBound => 1 contains_one_uni 'value_type', PAnyType end # @api public # class PStructType < PAnyType contains_many_uni 'elements', PStructElement, :lowerBound => 1 has_attr 'hashed_elements', Object, :derived => true end # @api public # class PTupleType < PAnyType contains_many_uni 'types', PAnyType, :lowerBound => 1 # If set, describes min and max required of the given types - if max > size of # types, the last type entry repeats # contains_one_uni 'size_type', PIntegerType, :lowerBound => 0 end # @api public # class PCallableType < PAnyType # Types of parameters as a Tuple with required/optional count, or an Integer with min (required), max count contains_one_uni 'param_types', PAnyType, :lowerBound => 1 # 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. # contains_one_uni 'block_type', PAnyType, :lowerBound => 0 end # @api public # class PArrayType < PCollectionType end # @api public # class PHashType < PCollectionType contains_one_uni 'key_type', PAnyType end RuntimeEnum = RGen::MetamodelBuilder::DataTypes::Enum.new( :name => 'RuntimeEnum', :literals => [:'ruby', ]) # @api public # class PRuntimeType < PAnyType has_attr 'runtime', RuntimeEnum, :lowerBound => 1 has_attr 'runtime_type_name', String end # Abstract representation of a type that can be placed in a Catalog. # @api public # class PCatalogEntryType < PAnyType end # Represents a (host-) class in the Puppet Language. # @api public # class PHostClassType < PCatalogEntryType has_attr 'class_name', String end # Represents a Resource Type in the Puppet Language # @api public # class PResourceType < PCatalogEntryType has_attr 'type_name', String has_attr 'title', String 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 < PAnyType contains_one_uni 'optional_type', PAnyType end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/utils.rb���������������������������������������������������������������0000664�0052762�0001160�00000010772�12650174557�017512� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Provides utility methods module Puppet::Pops::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 !!Puppet::Pops::Patterns::NUMERIC.match(relativize_name(o.to_s)) end end # Convert a match from Puppet::Pops::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 != Puppet::Pops::Types::TypeCalculator::TheInfinity 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 = Puppet::Pops::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 = Puppet::Pops::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? "::" end def self.name_to_segments name name.split("::") end def self.relativize_name name is_absolute?(name) ? name[2..-1] : name end # Finds an existing adapter for o or for one of its containers, or nil, if none of the containers # was adapted with the given adapter. # This method can only be used with objects that respond to `:eContainer`. # with true. # # @see #find_closest_positioned # def self.find_adapter(o, adapter) return nil if o.nil? || (o.is_a?(Array) && o.empty?) a = adapter.get(o) return a if a return find_adapter(o.eContainer, adapter) end # Finds the closest positioned Puppet::Pops::Model::Positioned object, or object decorated with # a SourcePosAdapter, and returns # a SourcePosAdapter for the first found, or nil if not found. # def self.find_closest_positioned(o) return nil if o.nil? || o.is_a?(Puppet::Pops::Model::Program) || (o.is_a?(Array) && o.empty?) return find_adapter(o, Puppet::Pops::Adapters::SourcePosAdapter) unless o.is_a?(Puppet::Pops::Model::Positioned) o.offset.nil? ? find_closest_positioned(o.eContainer) : Puppet::Pops::Adapters::SourcePosAdapter.adapt(o) end end ������puppet-3.8.5/lib/puppet/pops/validation.rb����������������������������������������������������������0000664�0052762�0001160�00000036757�12650174557�020517� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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 Puppet::Pops::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 sublcass 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) Puppet::Pops::Validation::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 Puppet::Pops::Validation::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 [Puppet::Pops::Issues::Issue] the issue for which to set severity # @param level [Symbol] the severity level (:error, :warning, or :ignore). # @api public # def []=(issue, level) raise Puppet::DevError.new("Attempt to set validation severity for something that is not an Issue. (Got #{issue.class})") unless issue.is_a? Puppet::Pops::Issues::Issue raise Puppet::DevError.new("Illegal severity level: #{option}") unless @@severity_hash[level] raise Puppet::DevError.new("Attempt to demote the hard issue '#{issue.issue_code}' to #{level}") unless issue.demotable? || level == :error @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 raise Puppet::DevError.new("Attempt to get validation severity for something that is not an Issue. (Got #{issue.class})") unless issue.is_a? Puppet::Pops::Issues::Issue end # Checks if the given severity level is valid. # @api private # def assert_severity level raise Puppet::DevError.new("Illegal severity level: #{option}") unless @@severity_hash[level] 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] ||= '' source_pos = Puppet::Pops::Utils.find_closest_positioned(semantic) file = source_pos ? source_pos.locator.file : 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 "#{loc(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 and 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 thwy were reported. def errors @diagnostics.select {|d| d.severity == :error } end # Returns the diagnosed warnings in the order thwy 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 thwy 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 [Puppet::Pops::Validation::Diagnostic, Puppet::Pops::Validation::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<Puppet::Pops::Validation::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 �����������������puppet-3.8.5/lib/puppet/pops/validation/������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020147� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/validation/checker4_0.rb�����������������������������������������������0000664�0052762�0001160�00000060360�12650174557�022411� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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 Puppet::Pops::Validation::Checker4_0 Issues = Puppet::Pops::Issues Model = Puppet::Pops::Model attr_reader :acceptor attr_reader :migration_checker # Initializes the validator with a diagnostics producer. This object must respond to # `:will_accept?` and `:accept`. # def initialize(diagnostics_producer) @@check_visitor ||= Puppet::Pops::Visitor.new(nil, "check", 0, 0) @@rvalue_visitor ||= Puppet::Pops::Visitor.new(nil, "rvalue", 0, 0) @@hostname_visitor ||= Puppet::Pops::Visitor.new(nil, "hostname", 1, 2) @@assignment_visitor ||= Puppet::Pops::Visitor.new(nil, "assign", 0, 1) @@query_visitor ||= Puppet::Pops::Visitor.new(nil, "query", 0, 0) @@top_visitor ||= Puppet::Pops::Visitor.new(nil, "top", 1, 1) @@relation_visitor ||= Puppet::Pops::Visitor.new(nil, "relation", 0, 0) @@idem_visitor ||= Puppet::Pops::Visitor.new(self, "idem", 0, 0) @acceptor = diagnostics_producer # Use null migration checker unless given in context @migration_checker ||= (Puppet.lookup(:migration_checker) { Puppet::Pops::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 check(model) model.eAllContents.each {|m| check(m) } 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, single_feature_name = nil) @@hostname_visitor.visit_this_2(self, o, semantic, single_feature_name) 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 # Performs check if this is valid as a container of a definition (class, define, node) def top(o, definition) @@top_visitor.visit_this_1(self, o, definition) 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?(Puppet::Pops::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 =~ Puppet::Pops::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). # TODO: Investigate if there are invalid cases for += assignment 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_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.current) 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 parent = o.eContainer unless parent.is_a?(Model::CollectExpression) || parent.is_a?(Model::ResourceOverrideExpression) acceptor.accept(Issues::ILLEGAL_ATTRIBUTE_APPEND, o, {:name=>o.attribute_name, :parent=>parent}) end end rvalue(o.value_expr) end def check_AttributesOperation(o) # Append operator use is constrained parent1 = o.eContainer case parent1 when Model::AbstractResource when Model::CollectExpression 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 = parent1.nil? ? o : parent1.eContainer 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 check_BlockExpression(o) 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 migration_checker.report_array_last_in_block(o.statements[-1]) end def check_CallNamedFunctionExpression(o) case o.functor_expr when Puppet::Pops::Model::QualifiedName # ok nil when Puppet::Pops::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_EppExpression(o) if o.eContainer.is_a?(Puppet::Pops::Model::LambdaExpression) internal_check_no_capture(o.eContainer, o) internal_check_parameter_name_uniqueness(o.eContainer) 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 should only be one LiteralDefault case option value # TODO: Implement this check 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 # If a collect expression tries to collect exported resources and storeconfigs is not on # then it will not work... This was checked in the parser previously. This is a runtime checking # thing as opposed to a language thing. if acceptor.will_accept?(Issues::RT_NO_STORECONFIGS) && o.query.is_a?(Model::ExportedQuery) acceptor.accept(Issues::RT_NO_STORECONFIGS, o) end end # Only used for function names, grammar should not be able to produce something faulty, but # check anyway if model is created programatically (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 => o.eContainer) 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 = { 'application' => true, 'produces' => true, 'consumes' => true } # for 'class', 'define', and function def check_NamedDefinition(o) top(o.eContainer, o) if o.name !~ Puppet::Pops::Patterns::CLASSREF acceptor.accept(Issues::ILLEGAL_DEFINITION_NAME, o, {:name=>o.name}) end if RESERVED_TYPE_NAMES[o.name()] acceptor.accept(Issues::RESERVED_TYPE_NAME, o, {:name => o.name}) end # This is perhaps not ideal but it's very difficult to pass a ReservedWord through # the mechanism that creates qualified names (namestack, namepop etc.) if FUTURE_RESERVED_WORDS[o.name] acceptor.accept(Issues::FUTURE_RESERVED_WORD, o, {:word => o.name}) end if violator = ends_with_idem(o.body) acceptor.accept(Issues::IDEM_NOT_ALLOWED_LAST, violator, {:container => o}) end end def check_HostClassDefinition(o) check_NamedDefinition(o) internal_check_no_capture(o) internal_check_parameter_name_uniqueness(o) internal_check_reserved_params(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) 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 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 => o.eContainer) end def check_LambdaExpression(o) internal_check_capture_last(o) end def check_LiteralList(o) o.values.each {|v| rvalue(v) } end def check_LiteralFloat(o) migration_checker.report_ambiguous_float(o) end def check_LiteralInteger(o) migration_checker.report_ambiguous_integer(o) end def check_NodeDefinition(o) # Check that hostnames are valid hostnames (or regular expressions) hostname(o.host_matches, o) hostname(o.parent, o, 'parent') unless o.parent.nil? top(o.eContainer, o) if violator = ends_with_idem(o.body) acceptor.accept(Issues::IDEM_NOT_ALLOWED_LAST, violator, {:container => o}) 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.value !~ Puppet::Pops::Patterns::CLASSREF acceptor.accept(Issues::ILLEGAL_CLASSREF, o, {:name=>o.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 =~ Puppet::Pops::Patterns::PARAM_NAME acceptor.accept(Issues::ILLEGAL_PARAM_NAME, o, :name => o.name) 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). end def check_ResourceBody(o) seenUnfolding = false o.operations.each do |ao| if ao.is_a?(Puppet::Pops::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 && o.form != :regular acceptor.accept(Issues::NOT_VIRTUALIZEABLE, o) end end def check_ResourceOverrideExpression(o) if o.form && 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) 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 =~ Puppet::Pops::Patterns::NUMERIC_VAR_NAME acceptor.accept(Issues::ILLEGAL_NUMERIC_VAR_NAME, o, :name => name) end else unless name =~ Puppet::Pops::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, single_feature_name) if single_feature_name acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, {:feature=>single_feature_name, :container=>semantic}) end o.each {|x| hostname(x, semantic, false) } end def hostname_String(o, semantic, single_feature_name) # 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 =~ Puppet::Pops::Patterns::ILLEGAL_HOSTNAME_CHARS acceptor.accept(Issues::ILLEGAL_HOSTNAME_CHARS, semantic, :hostname => o) end end def hostname_LiteralValue(o, semantic, single_feature_name) hostname_String(o.value.to_s, o, single_feature_name) end def hostname_ConcatenatedString(o, semantic, single_feature_name) # 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], false) end end def hostname_QualifiedName(o, semantic, single_feature_name) hostname_String(o.value.to_s, o, single_feature_name) end def hostname_QualifiedReference(o, semantic, single_feature_name) hostname_String(o.value.to_s, o, single_feature_name) end def hostname_LiteralNumber(o, semantic, single_feature_name) # always ok end def hostname_LiteralDefault(o, semantic, single_feature_name) # always ok end def hostname_LiteralRegularExpression(o, semantic, single_feature_name) # always ok end def hostname_Object(o, semantic, single_feature_name) acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, {:feature=> single_feature_name || '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 #---TOP CHECK def top_NilClass(o, definition) # ok, reached the top, no more parents end def top_Object(o, definition) # fail, reached a container that is not top level acceptor.accept(Issues::NOT_TOP_LEVEL, definition) end def top_BlockExpression(o, definition) # ok, if this is a block representing the body of a class, or is top level top o.eContainer, definition end def top_HostClassDefinition(o, definition) # ok, stop scanning parents end def top_Program(o, definition) # ok end # A LambdaExpression is a BlockExpression, and this method 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. # def top_LambdaExpression(o, definition) # fail, stop scanning parents acceptor.accept(Issues::NOT_TOP_LEVEL, definition) 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.current) end def idem_AccessExpression(o) true end def idem_BinaryExpression(o) true 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 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/pops/validation/validator_factory_4_0.rb������������������������������������0000664�0052762�0001160�00000001734�12650174557�024660� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Configures validation suitable for 4.0 # class Puppet::Pops::Validation::ValidatorFactory_4_0 < Puppet::Pops::Validation::Factory Issues = Puppet::Pops::Issues # Produces the checker to use def checker diagnostic_producer Puppet::Pops::Validation::Checker4_0.new(diagnostic_producer) end # Produces the label provider to use def label_provider Puppet::Pops::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::NAME_WITH_HYPHEN] = :error p[Issues::EMPTY_RESOURCE_SPECIALIZATION] = :ignore p end end ������������������������������������puppet-3.8.5/lib/puppet/pops/visitable.rb�����������������������������������������������������������0000664�0052762�0001160�00000000270�12650174557�020324� 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-3.8.5/lib/puppet/pops/visitor.rb�������������������������������������������������������������0000664�0052762�0001160�00000006214�12650174557�020045� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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. # vistor = 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 Puppet::Pops::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 # 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| method_name = :"#{@message}_#{ancestor.name.split(/::/).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 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) 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 th 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 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/property.rb�����������������������������������������������������������������0000664�0052762�0001160�00000066477�12650174557�017272� 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). # # @todo Describe meta-parameter shadowing. This concept can not be understood by just looking at the descriptions # of the methods involved. # # @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) raise ArgumentError, "Supported values for Property#array_matching are 'first' and 'all'" unless [:first, :all].include?(value) @array_matching = 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] :call When to call any associated block. The default value is `:instead` which # means that the block should be called instead of the provider. In earlier versions (before 20081031) it # was possible to specify a value of `:before` or `:after` for the purpose of calling # both the block and the provider. Use of these deprecated options will now raise an exception later # in the process when the _is_ value is set (see #set). # @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). # @todo The original documentation states that the option `:method` will set the name of the generated # setter method, but this is not implemented. Is the documentatin or the implementation in error? # (The implementation is in Puppet::Parameter::ValueCollection#new_value). # @todo verify that the use of :before and :after have been deprecated (or rather - never worked, and # was never in use. (This means, that the option :call could be removed since calls are always :instead). # # @dsl type # @api public def self.newvalue(name, options = {}, &block) value = value_collection.newvalue(name, options, &block) define_method(value.method, &value.block) if value.method and value.block 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) 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 # Sets the value of this property to the given value by calling the dynamically created setter method associated with the "valid value" referenced by the given name. # @param name [Symbol, Regexp] a valid value "name" as returned by {value_name} # @param value [Object] the value to set as the value of the property # @raise [Puppet::DevError] if there was no method to call # @raise [Puppet::Error] if there were problems setting the value # @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. # @todo The check for a valid value option called `:method` does not seem to be fully supported # as it seems that this option is never consulted when the method is dynamically created. Needs to # be investigated. (Bug, or documentation needs to be changed). # @see #set # @api private # def call_valuemethod(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 #{self.class.name}: #{detail}", @resource.line, @resource.file, 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 devfail "Could not find method for value '#{name}'" end 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 #{self.class.format_value_for_display should_to_s(newvalue)}" elsif newvalue == :absent or newvalue == [:absent] return "undefined '#{name}' from #{self.class.format_value_for_display is_to_s(current_value)}" else return "#{name} changed #{self.class.format_value_for_display is_to_s(current_value)} to #{self.class.format_value_for_display should_to_s(newvalue)}" end rescue Puppet::Error, Puppet::DevError raise rescue => detail message = "Could not convert change '#{name}' to string: #{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 _path_ (?? See todo) # * `:invalidate_refreshes` - if scheduled refreshes should be invalidated # # @todo What is the intent of this method? What is the meaning of the :source_description passed in the # options to the created event? # @return [Puppet::Transaction::Event] the created event # @see Puppet::Type#event def event attrs = { :name => event_name, :desired_value => should, :property => self, :source_description => path } if should and value = self.class.value_collection.match?(should) attrs[:invalidate_refreshes] = true if value.invalidate_refreshes end resource.event attrs end # @todo What is this? # What is this used for? attr_reader :shadow # Initializes a Property the same way as a Parameter and handles the special case when a property is shadowing a meta-parameter. # @todo There is some special initialization when a property is not a metaparameter but # Puppet::Type.metaparamclass(for this class's name) is not nil - if that is the case a # setup_shadow is performed for that class. # # @param hash [Hash] options passed to the super initializer {Puppet::Parameter#initialize} # @note New properties of a type should be created via the DSL method {Puppet::Type.newproperty}. # @see Puppet::Parameter#initialize description of Parameter initialize options. # @api private def initialize(hash = {}) super if ! self.metaparam? and klass = Puppet::Type.metaparamclass(self.class.name) setup_shadow(klass) end 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 # 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 simply returns the given argument. A derived implementation # may perform property specific pretty printing when the _is_ and _should_ values are not # already in suitable form. # @return [String] a pretty printing string def is_to_s(currentvalue) currentvalue 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 # (see Puppet::Parameter#munge) # If this property is a meta-parameter shadow, the shadow's munge is also called. # @todo Incomprehensible ! The concept of "meta-parameter-shadowing" needs to be explained. # def munge(value) self.shadow.munge(value) if self.shadow super 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 value is set using the provider's setter method for this property ({#call_provider}) if nothing # else has been specified. If the _valid value_ for the given value defines a `:call` option with the # value `:instead`, the # value is set with {#call_valuemethod} which invokes a block specified for the valid value. # # @note In older versions (before 20081031) it was possible to specify the call types `:before` and `:after` # which had the effect that both the provider method and the _valid value_ block were called. # This is no longer supported. # # @param value [Object] the value to set as the value of this property # @return [Object] returns what {#call_valuemethod} or {#call_provider} returns # @raise [Puppet::Error] when the provider setter should be used but there is no provider set in the _associated # resource_ # @raise [Puppet::DevError] when a deprecated call form was specified (e.g. `:before` or `:after`). # @api public # def set(value) # Set a name for looking up associated options like the event. name = self.class.value_name(value) call = self.class.value_option(name, :call) || :none if call == :instead call_valuemethod(name, value) elsif call == :none # They haven't provided a block, and our parent does not have # a provider, so we have no idea how to handle this. self.fail "#{self.class.name} cannot handle values of type #{value.inspect}" unless @resource.provider call_provider(value) else # LAK:NOTE 20081031 This is a change in behaviour -- you could # previously specify :call => [;before|:after], which would call # the setter *in addition to* the block. I'm convinced this # was never used, and it makes things unecessarily complicated. # If you want to specify a block and still call the setter, then # do so in the block. devfail "Cannot use obsolete :call value '#{call}' for property '#{self.class.name}'" end end # Sets up a shadow property for a shadowing meta-parameter. # This construct allows the creation of a property with the # same name as a meta-parameter. The metaparam will only be stored as a shadow. # @param klass [Class<inherits Puppet::Parameter>] the class of the shadowed meta-parameter # @return [Puppet::Parameter] an instance of the given class (a parameter or property) # def setup_shadow(klass) @shadow = klass.new(:resource => self.resource) 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 # Formats the given newvalue (following _should_ type conventions) for inclusion in a string describing a change. # @return [String] Returns the given newvalue in string form with space separated entries if it is an array. # @see #change_to_s # def should_to_s(newvalue) [newvalue].flatten.join(" ") 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(", ") raise ArgumentError, "Provider must have features '#{needed_features}' to set '#{self.class.name}' to '#{value}'" unless provider.satisfies?(features) 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-3.8.5/lib/puppet/property/�������������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�016720� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/property/boolean.rb���������������������������������������������������������0000664�0052762�0001160�00000000226�12650174557�020665� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/coercion' class Puppet::Property::Boolean < Puppet::Property def unsafe_munge(value) Puppet::Coercion.boolean(value) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/property/ensure.rb����������������������������������������������������������0000664�0052762�0001160�00000006036�12650174557�020554� 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 or currentvalue.nil? return "created" elsif newvalue == :absent return "removed" else return "#{self.name} changed '#{self.is_to_s(currentvalue)}' to '#{self.should_to_s(newvalue)}'" end rescue Puppet::Error, Puppet::DevError raise rescue => detail raise Puppet::DevError, "Could not convert change #{self.name} to string: #{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 #{@resource.class.name} exists" 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-3.8.5/lib/puppet/property/keyvalue.rb��������������������������������������������������������0000664�0052762�0001160�00000005414�12650174557�021077� 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 def hash_to_key_value_s(hash) hash.select { |k,v| true }.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(key_value_array) #turns string array into a hash key_value_array.inject({}) do |hash, key_value| tmp = key_value.split(separator) hash[tmp[0].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 current.merge(members) 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 end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/property/list.rb������������������������������������������������������������0000664�0052762�0001160�00000003453�12650174557�020226� 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 should_to_s(should_value) #just return the should value should_value end def is_to_s(currentvalue) if currentvalue == :absent return "absent" else return currentvalue.join(delimiter) end 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-3.8.5/lib/puppet/property/ordered_list.rb����������������������������������������������������0000664�0052762�0001160�00000001474�12650174557�021733� 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-3.8.5/lib/puppet/provider.rb�����������������������������������������������������������������0000664�0052762�0001160�00000062617�12650174557�017230� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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 Original = _"LAK 2007-05-09: Keep the model stuff around for backward compatibility"_ # Is this really needed? The comment about backwards compatibility was made in 2007. # # @return [???] A model kept for backwards compatibility. # @api private # @deprecated This attribute is available for backwards compatibility reasons. attr_reader :model # @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 # @todo original = _"LAK 2007-05-09: Keep the model stuff around for backward compatibility"_, why is it # both here (instance) and at class level? Is this a different model? # @return [???] model is WHAT? attr_reader :model # @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. # @see execute # @return (see 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. # @see execpipe # @return (see 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. # @see execfail # @return (see execfail) def execfail(*args) Puppet::Util::Execution.execfail(*args) end # (see Puppet::Util::Execution.execfail) 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 #{name} defined for 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 def self.fact_match(fact, values) values = [values] unless values.is_a? Array values.map! { |v| v.to_s.downcase.intern } if fval = Facter.value(fact).to_s and fval != "" fval = fval.to_s.downcase.intern values.include?(fval) else false 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<Puppet::Provider>] 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 #{self.name} has not defined the 'instances' class method" end # Creates the methods for a given command. # @api private # @deprecated Use {commands}, {optional_commands}, or {has_command} instead. This was not meant to be part of a public API def self.make_command_methods(name) Puppet.deprecation_warning "Provider.make_command_methods is deprecated; use Provider.commands or Provider.optional_commands instead for creating command methods" # Now define a method for that command unless singleton_class.method_defined?(name) meta_def(name) do |*args| # This might throw an ExecutionFailure, but the system above # will catch it, if so. command = Puppet::Provider::Command.new(name, command(name), Puppet::Util, Puppet::Util::Execution) return command.execute(*args) end # And then define an instance method that just calls the class method. # We need both, so both instances and classes can easily run the commands. unless method_defined?(name) define_method(name) do |*args| self.class.send(name, *args) end end end 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, "'#{param}' is not a valid parameter for #{resource_type.name}" end end return true unless features = klass.required_features !!satisfies?(*features) end # def self.to_s # unless defined?(@str) # if self.resource_type # @str = "#{resource_type.name} provider #{self.name}" # else # @str = "unattached provider #{self.name}" # end # end # @str # 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 @model = 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 exaplanation; 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 # LAK 2007-05-09: Keep the model stuff around for backward compatibility @model = 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 #{self.class.name} instance" end end # Sets the given parameters values as the current values for those parameters. # Other parameters are unchanged. # @param [Array<Puppet::Parameter>] 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 # 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-3.8.5/lib/puppet/provider/�������������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�016666� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/aixobject.rb�������������������������������������������������������0000664�0052762�0001160�00000030106�12650174557�021164� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # Common code for AIX providers. This class implements basic structure for # AIX resources. # Author:: Hector Rivas Gandara <keymon@gmail.com> # class Puppet::Provider::AixObject < Puppet::Provider desc "Generic AIX resource provider" # The real provider must implement these functions. def lscmd( _value = @resource[:name] ) raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: Base AixObject provider doesn't implement lscmd" end def addcmd( _extra_attrs = [] ) raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: Base AixObject provider doesn't implement addcmd" end def modifycmd( _attributes_hash = {} ) raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: Base AixObject provider doesn't implement modifycmd" end def deletecmd raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: Base AixObject provider doesn't implement deletecmd" end # Valid attributes to be managed by this provider. # It is a list of hashes # :aix_attr AIX command attribute name # :puppet_prop Puppet propertie name # :to Optional. Method name that adapts puppet property to aix command value. # :from Optional. Method to adapt aix command line value to puppet property. Optional class << self attr_accessor :attribute_mapping end # Mapping from Puppet property to AIX attribute. def self.attribute_mapping_to if ! @attribute_mapping_to @attribute_mapping_to = {} attribute_mapping.each { |elem| attribute_mapping_to[elem[:puppet_prop]] = { :key => elem[:aix_attr], :method => elem[:to] } } end @attribute_mapping_to end # Mapping from AIX attribute to Puppet property. def self.attribute_mapping_from if ! @attribute_mapping_from @attribute_mapping_from = {} attribute_mapping.each { |elem| attribute_mapping_from[elem[:aix_attr]] = { :key => elem[:puppet_prop], :method => elem[:from] } } end @attribute_mapping_from end # This functions translates a key and value using the given mapping. # Mapping can be nil (no translation) or a hash with this format # {:key => new_key, :method => translate_method} # It returns a list with the pair [key, value] def translate_attr(key, value, mapping) return [key, value] unless mapping return nil unless mapping[key] if mapping[key][:method] new_value = method(mapping[key][:method]).call(value) else new_value = value end [mapping[key][:key], new_value] end # Loads an AIX attribute (key=value) and stores it in the given hash with # puppet semantics. It translates the pair using the given mapping. # # This operation works with each property one by one, # subclasses must reimplement this if more complex operations are needed def load_attribute(key, value, mapping, objectinfo) if mapping.nil? objectinfo[key] = value elsif mapping[key].nil? # is not present in mapping, ignore it. true elsif mapping[key][:method].nil? objectinfo[mapping[key][:key]] = value elsif objectinfo[mapping[key][:key]] = method(mapping[key][:method]).call(value) end return objectinfo end # Gets the given command line argument for the given key and value, # using the given mapping to translate key and value. # All the objectinfo hash (@resource or @property_hash) is passed. # # This operation works with each property one by one, # and default behaviour is return the arguments as key=value pairs. # Subclasses must reimplement this if more complex operations/arguments # are needed # def get_arguments(key, value, mapping, objectinfo) if mapping.nil? new_key = key new_value = value elsif mapping[key].nil? # is not present in mapping, ignore it. new_key = nil new_value = nil elsif mapping[key][:method].nil? new_key = mapping[key][:key] new_value = value elsif new_key = mapping[key][:key] new_value = method(mapping[key][:method]).call(value) end # convert it to string new_value = Array(new_value).join(',') if new_key return [ "#{new_key}=#{new_value}" ] else return [] end end # Convert the provider properties (hash) to AIX command arguments # (list of strings) # This function will translate each value/key and generate the argument using # the get_arguments function. def hash2args(hash, mapping=self.class.attribute_mapping_to) return "" unless hash arg_list = [] hash.each {|key, val| arg_list += self.get_arguments(key, val, mapping, hash) } arg_list end # Parse AIX command attributes from the output of an AIX command, that # which format is a list of space separated of key=value pairs: # "uid=100 groups=a,b,c". # It returns a hash. # # If a mapping is provided, the keys are translated as defined in the # mapping hash. And only values included in mapping will be added # # NOTE: it will ignore the items not including '=' def parse_attr_list(str, mapping=self.class.attribute_mapping_from) properties = {} attrs = [] if str.nil? or (attrs = str.split()).empty? return nil end attrs.each { |i| if i.include? "=" # Ignore if it does not include '=' (key_str, val) = i.split('=') # Check the key if key_str.nil? or key_str.empty? info "Empty key in string 'i'?" continue end key_str.strip! key = key_str.to_sym val.strip! if val properties = self.load_attribute(key, val, mapping, properties) end } properties.empty? ? nil : properties end # Parse AIX command output in a colon separated list of attributes, # This function is useful to parse the output of commands like lsfs -c: # #MountPoint:Device:Vfs:Nodename:Type:Size:Options:AutoMount:Acct # /:/dev/hd4:jfs2::bootfs:557056:rw:yes:no # /home:/dev/hd1:jfs2:::2129920:rw:yes:no # /usr:/dev/hd2:jfs2::bootfs:9797632:rw:yes:no # # If a mapping is provided, the keys are translated as defined in the # mapping hash. And only values included in mapping will be added def parse_colon_list(str, key_list, mapping=self.class.attribute_mapping_from) properties = {} attrs = [] if str.nil? or (attrs = str.split(':')).empty? return nil end attrs.each { |val| key = key_list.shift.downcase.to_sym properties = self.load_attribute(key, val, mapping, properties) } properties.empty? ? nil : properties end # Default parsing function for AIX commands. # It will choose the method depending of the first line. # For the colon separated list it will: # 1. Get keys from first line. # 2. Parse next line. def parse_command_output(output, mapping=self.class.attribute_mapping_from) lines = output.split("\n") # if it begins with #something:... is a colon separated list. if lines[0] =~ /^#.*:/ self.parse_colon_list(lines[1], lines[0][1..-1].split(':'), mapping) else self.parse_attr_list(lines[0], mapping) end end # Retrieve all the information of an existing resource. # It will execute 'lscmd' command and parse the output, using the mapping # 'attribute_mapping_from' to translate the keys and values. def getinfo(refresh = false) if @objectinfo.nil? or refresh == true # Execute lsuser, split all attributes and add them to a dict. begin output = execute(self.lscmd) @objectinfo = self.parse_command_output(output) # All attributtes without translation @objectosinfo = self.parse_command_output(output, nil) rescue Puppet::ExecutionFailure => detail # Print error if needed. FIXME: Do not check the user here. Puppet.debug "aix.getinfo(): Could not find #{@resource.class.name} #{@resource.name}: #{detail}" end end @objectinfo end # Like getinfo, but it will not use the mapping to translate the keys and values. # It might be usefult to retrieve some raw information. def getosinfo(refresh = false) if @objectosinfo.nil? or refresh == true getinfo(refresh) end @objectosinfo || Hash.new end # List all elements of given type. It works for colon separated commands and # list commands. # It returns a list of names. def self.list_all names = [] begin output = execute([self.command(:list), 'ALL']) output = output.split("\n").select{ |line| line != /^#/ } output.each do |line| name = line.split(/[ :]/)[0] names << name if not name.empty? end rescue Puppet::ExecutionFailure => detail # Print error if needed Puppet.debug "aix.list_all(): Could not get all resources of type #{@resource.class.name}: #{detail}" end names end #------------- # Provider API # ------------ # Clear out the cached values. def flush @property_hash.clear if @property_hash @objectinfo.clear if @objectinfo end # Check that the user exists def exists? !!getinfo(true) # !! => converts to bool end # Return all existing instances # The method for returning a list of provider instances. Note that it returns # providers, preferably with values already filled in, not resources. def self.instances objects=[] list_all.each { |entry| objects << new(:name => entry, :ensure => :present) } objects end #- **ensure** # The basic state that the object should be in. Valid values are # `present`, `absent`, `role`. # From ensurable: exists?, create, delete def ensure if exists? :present else :absent end end # Create a new instance of the resource def create if exists? info "already exists" # The object already exists return nil end begin execute(self.addcmd) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not create #{@resource.class.name} #{@resource.name}: #{detail}", detail.backtrace end end # Delete this instance of the resource def delete unless exists? info "already absent" # the object already doesn't exist return nil end begin execute(self.deletecmd) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not delete #{@resource.class.name} #{@resource.name}: #{detail}", detail.backtrace end end #-------------------------------- # Call this method when the object is initialized. # It creates getter/setter methods for each property our resource type supports. # If setter or getter already defined it will not be overwritten def self.mk_resource_methods [resource_type.validproperties, resource_type.parameters].flatten.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 # Define the needed getters and setters as soon as we know the resource type def self.resource_type=(resource_type) super mk_resource_methods end # Retrieve a specific value by name. def get(param) (hash = getinfo(false)) ? hash[param] : nil end # Set a property. def set(param, value) @property_hash[param.intern] = value if getinfo().nil? # This is weird... raise Puppet::Error, "Trying to update parameter '#{param}' to '#{value}' for a resource that does not exists #{@resource.class.name} #{@resource.name}: #{detail}" end if value == getinfo()[param.to_sym] return end #self.class.validate(param, value) if cmd = modifycmd({param =>value}) begin execute(cmd) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}", detail.backtrace end end # Refresh de info. getinfo(true) end def initialize(resource) super @objectinfo = nil @objectosinfo = nil end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/augeas/������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020133� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/augeas/augeas.rb���������������������������������������������������0000664�0052762�0001160�00000041340�12650174557�021730� 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 # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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 ], "mv" => [ :path, :path ], "insert" => [ :string, :string, :path ], "get" => [ :path, :comparator, :string ], "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}") 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}") 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}]|(\\.))*/) sc.getch else argline << sc.scan(/[^\s]+/) end fail("missing string argument #{narg} for #{cmd}") unless argline[-1] elsif f == :comparator argline << sc.scan(/(==|!=|=~|<=|>=|<|>)/) unless argline[-1] puts sc.rest fail("invalid comparator for command #{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] restricted = false 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 Fixnum 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_array.join(" ")}") if cmd_array.length < 4 cmd = 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 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_array.join(" ")}") if cmd_array.length < 3 cmd = 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}'") if (result == -1) # Now do the work case verb when "size" fail("Invalid command: #{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 = eval arg return_value = (result == new_array) rescue fail("Invalid array in command: #{cmd_array.join(" ")}") end when "!=" begin arg = clause_array.shift new_array = eval arg return_value = (result != new_array) rescue fail("Invalid array in command: #{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 "match"; return_value = process_match(cmd_array) end rescue SystemExit,NoMemoryError raise rescue Exception => e fail("Error sending command '#{command}' with params #{cmd_array[1..-1].inspect}/#{e.message}") end end unless force # If we have a verison 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_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 #{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 #{cmd_array.inspect}") if (rv == -1) else fail("command '#{command}' not supported in installed version of ruby-augeas") 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 #{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 #{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 #{cmd_array.inspect}") if (!rv) else fail("command '#{command}' not supported in installed version of ruby-augeas") 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") end debug("sending command '#{command}' with params #{[label, where, path].inspect}") rv = aug.insert(path, label, before) fail("Error sending command '#{command}' with 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 #{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 #{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 #{cmd_array.inspect}") if (rv == -1) else fail("Command '#{command}' is not supported") end rescue SystemExit,NoMemoryError raise rescue Exception => e fail("Error sending command '#{command}' with params #{cmd_array.inspect}/#{e.message}") end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/cisco.rb�����������������������������������������������������������0000664�0052762�0001160�00000000467�12650174557�020323� 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-3.8.5/lib/puppet/provider/command.rb���������������������������������������������������������0000664�0052762�0001160�00000002077�12650174557�020640� 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::Error, "Command #{@name} is missing" @executor.execute([resolved_executable] + args, @options) end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/computer/����������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020524� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/computer/computer.rb�����������������������������������������������0000664�0052762�0001160�00000001460�12650174557�022711� 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-3.8.5/lib/puppet/provider/confine.rb���������������������������������������������������������0000664�0052762�0001160�00000000415�12650174557�020635� 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-3.8.5/lib/puppet/provider/cron/��������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017627� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/cron/crontab.rb����������������������������������������������������0000664�0052762�0001160�00000017721�12650174557�021615� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/parsedfile' Puppet::Type.type(:cron).provide(:crontab, :parent => Puppet::Provider::ParsedFile, :default_target => ENV["USER"] || "root") 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+=} 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 end �����������������������������������������������puppet-3.8.5/lib/puppet/provider/exec.rb������������������������������������������������������������0000664�0052762�0001160�00000007210�12650174557�020140� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider' require 'puppet/util/execution' class Puppet::Provider::Exec < Puppet::Provider include Puppet::Util::Execution def run(command, check = false) output = nil status = nil dir = nil checkexe(command) if dir = resource[:cwd] unless File.directory?(dir) if check dir = nil else self.fail "Working directory '#{dir}' does not exist" end end end dir ||= Dir.pwd debug "Executing#{check ? " check": ""} '#{command}'" begin # Do our chdir Dir.chdir(dir) do environment = {} environment[:PATH] = resource[:path].join(File::PATH_SEPARATOR) if resource[:path] if envlist = resource[:environment] envlist = [envlist] unless envlist.is_a? Array envlist.each do |setting| if setting =~ /^(\w+)=((.|\n)+)$/ env_name = $1 value = $2 if environment.include?(env_name) || environment.include?(env_name.to_sym) warning "Overriding environment setting '#{env_name}' with '#{value}'" end environment[env_name] = value else warning "Cannot understand environment setting #{setting.inspect}" end end end if Puppet.features.microsoft_windows? exec_user = resource[:user] # Etc.getpwuid() returns nil on Windows elsif resource.current_username == resource[:user] exec_user = nil else exec_user = resource[:user] end # 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 # 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, :uid => exec_user, :gid => resource[:group], :override_locale => false, :custom_environment => environment) end # The shell returns 127 if the command is missing. if output.exitstatus == 127 raise ArgumentError, output end end rescue Errno::ENOENT => detail self.fail Puppet::Error, detail.to_s, detail 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." if !absolute_path?(exe) and resource[:path].nil? end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/exec/��������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017612� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/exec/posix.rb������������������������������������������������������0000664�0052762�0001160�00000002737�12650174557�021313� 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}'" elsif !File.file?(exe) raise ArgumentError, "'#{exe}' is a #{File.ftype(exe)}, not a file" elsif !File.executable?(exe) raise ArgumentError, "'#{exe}' is not executable" 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}'" 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-3.8.5/lib/puppet/provider/exec/shell.rb������������������������������������������������������0000664�0052762�0001160�00000001374�12650174557�021254� 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-3.8.5/lib/puppet/provider/exec/windows.rb����������������������������������������������������0000664�0052762�0001160�00000003404�12650174557�021633� 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}'" elsif !File.file?(exe) raise ArgumentError, "'#{exe}' is a #{File.ftype(exe)}, not a file" end return end if resource[:path] Puppet::Util.withenv :PATH => resource[:path].join(File::PATH_SEPARATOR) do return if which(exe) end end raise ArgumentError, "Could not find command '#{exe}'" end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/file/��������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017605� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/file/posix.rb������������������������������������������������������0000664�0052762�0001160�00000006324�12650174557�021302� 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 = :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}", 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 = :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}", detail.backtrace end end def mode if stat = resource.stat return (stat.mode & 007777).to_s(8) 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 #{resource[:path]}: #{detail.message}") error.set_backtrace detail.backtrace raise error end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/file/windows.rb����������������������������������������������������0000664�0052762�0001160�00000005330�12650174557�021626� 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}", 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}", detail.backtrace end end def mode if resource.stat mode = get_mode(resource[:path]) mode ? mode.to_s(8) : :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 #{resource[:path]}: #{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-3.8.5/lib/puppet/provider/group/�������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020022� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/group/aix.rb�������������������������������������������������������0000664�0052762�0001160�00000007706�12650174557�021143� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # Group Puppet provider for AIX. It uses standard commands to manage groups: # mkgroup, rmgroup, lsgroup, chgroup # # Author:: Hector Rivas Gandara <keymon@gmail.com> # require 'puppet/provider/aixobject' 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 # Provider features has_features :manages_aix_lam has_features :manages_members # 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" # Group attributes to ignore def self.attribute_ignore [] end # AIX attributes to properties mapping. # # Valid attributes to be managed by this provider. # It is a list with of hash # :aix_attr AIX command attribute name # :puppet_prop Puppet propertie name # :to Method to adapt puppet property to aix command value. Optional. # :from Method to adapt aix command value to puppet property. Optional self.attribute_mapping = [ #:name => :name, {:aix_attr => :id, :puppet_prop => :gid }, {:aix_attr => :users, :puppet_prop => :members, :from => :users_from_attr}, {:aix_attr => :attributes, :puppet_prop => :attributes}, ] #-------------- # Command definition # Return the IA module arguments based on the resource param ia_load_module def get_ia_module_args if @resource[:ia_load_module] ["-R", @resource[:ia_load_module].to_s] else [] end end def lscmd(value=@resource[:name]) [self.class.command(:list)] + self.get_ia_module_args + [ value] end def lsallcmd() lscmd("ALL") end def addcmd(extra_attrs = []) # Here we use the @resource.to_hash to get the list of provided parameters # Puppet does not call to self.<parameter>= method if it does not exists. # # It gets an extra list of arguments to add to the user. [self.class.command(:add) ] + self.get_ia_module_args + self.hash2args(@resource.to_hash) + extra_attrs + [@resource[:name]] end def modifycmd(hash = property_hash) args = self.hash2args(hash) return nil if args.empty? [self.class.command(:modify)] + self.get_ia_module_args + args + [@resource[:name]] end def deletecmd [self.class.command(:delete)] + self.get_ia_module_args + [@resource[:name]] end #-------------- # Overwrite get_arguments to add the attributes arguments def get_arguments(key, value, mapping, objectinfo) # In the case of attributes, return a list of key=vlaue if key == :attributes raise Puppet::Error, "Attributes must be a list of pairs key=value on #{@resource.class.name}[#{@resource.name}]" \ unless value and value.is_a? Hash return value.select { |k,v| true }.map { |pair| pair.join("=") } end super(key, value, mapping, objectinfo) end def filter_attributes(hash) # Return only not managed attributtes. hash.select { |k,v| !self.class.attribute_mapping_from.include?(k) and !self.class.attribute_ignore.include?(k) }.inject({}) { |hash, array| hash[array[0]] = array[1]; hash } end def attributes filter_attributes(getosinfo(false)) end def attributes=(attr_hash) #self.class.validate(param, value) param = :attributes cmd = modifycmd({param => filter_attributes(attr_hash)}) if cmd begin execute(cmd) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}", detail.backtrace end end end # Force convert users it a list. def users_from_attr(value) (value.is_a? String) ? value.split(',') : value end end ����������������������������������������������������������puppet-3.8.5/lib/puppet/provider/group/directoryservice.rb������������������������������������������0000664�0052762�0001160�00000000600�12650174557�023731� 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 end ��������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/group/groupadd.rb��������������������������������������������������0000664�0052762�0001160�00000004441�12650174557�022160� 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" 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.should(:gid).to_s} already exists, use allowdupe to force group creation") 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 end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/group/ldap.rb������������������������������������������������������0000664�0052762�0001160�00000003143�12650174557�021271� 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-3.8.5/lib/puppet/provider/group/pw.rb��������������������������������������������������������0000664�0052762�0001160�00000002205�12650174557�020775� 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-3.8.5/lib/puppet/provider/group/windows_adsi.rb����������������������������������������������0000664�0052762�0001160�00000005426�12650174557�023051� 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_users = Puppet::Util::Windows::ADSI::Group.name_sid_hash(current) specified_users = Puppet::Util::Windows::ADSI::Group.name_sid_hash(should) if @resource[:auth_membership] current_users == specified_users else return true if specified_users.empty? (specified_users.keys.to_a & current_users.keys.to_a) == specified_users.keys.to_a 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_sid_object(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.to_s})") "#{sid.domain}\\#{account}" end return users.join(',') end def member_valid?(user_name) ! Puppet::Util::Windows::SID.name_to_sid_object(user_name).nil? end def group @group ||= Puppet::Util::Windows::ADSI::Group.new(@resource[:name]) end def members group.members 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-3.8.5/lib/puppet/provider/host/��������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017643� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/host/parsed.rb�����������������������������������������������������0000664�0052762�0001160�00000002672�12650174557�021456� 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*$/ record_line :parsed, :fields => %w{ip name host_aliases comment}, :optional => %w{host_aliases comment}, :match => /^(\S+)\s+(\S+)\s*(.*?)?(?:\s*#\s*(.*))?$/, :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, "#{n} is a required attribute for hosts" 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 } end ����������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/interface/���������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020626� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/interface/cisco.rb�������������������������������������������������0000664�0052762�0001160�00000001017�12650174557�022253� 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-3.8.5/lib/puppet/provider/ldap.rb������������������������������������������������������������0000664�0052762�0001160�00000006767�12650174557�020154� 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 #{self.class}") unless self.class.manager raise(Puppet::DevError, "Invalid LDAP Configuration defined for #{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-3.8.5/lib/puppet/provider/macauthorization/��������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�022247� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/macauthorization/macauthorization.rb�������������������������������0000664�0052762�0001160�00000021703�12650174557�026161� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'facter' require 'facter/util/plist' 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" commands :sw_vers => "/usr/bin/sw_vers" confine :operatingsystem => :darwin # This should be confined based on macosx_productversion # but puppet resource doesn't make the facts available and # that interface is heavily used with this provider. if Puppet::FileSystem.exist?("/usr/bin/sw_vers") product_version = sw_vers "-productVersion" confine :true => unless /^10\.[0-4]/.match(product_version) true end end 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 = Plist::parse_xml(AuthDB) raise Puppet::Error.new("Cannot parse: #{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 = Plist::parse_xml(AuthDB) authdb_rules = authdb["rules"].dup if authdb_rules[resource[:name]] begin authdb["rules"].delete(resource[:name]) Plist::Emit.save_plist(authdb, AuthDB) rescue Errno::EACCES => e raise Puppet::Error.new("Error saving #{AuthDB}: #{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 quering the db once more. cmds = [] cmds << :security << "authorizationdb" << "read" << resource[:name] output = execute(cmds, :failonfail => false, :combine => false) current_values = Plist::parse_xml(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 = Plist::parse_xml(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 Plist::Emit.save_plist(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 #{tmp.path}: #{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 = Plist::parse_xml(AuthDB) authdb["rules"][name] = values begin Plist::Emit.save_plist(authdb, AuthDB) rescue raise Puppet::Error.new("Error writing to: #{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") 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[:name]} is unknown type.") 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-3.8.5/lib/puppet/provider/mailalias/���������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020622� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/mailalias/aliases.rb�����������������������������������������������0000664�0052762�0001160�00000001723�12650174557�022574� 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) record[:recipient] = record[:recipient].split(/\s*,\s*/).collect { |d| d.gsub(/^['"]|['"]$/, '') } record end def process(line) ret = {} records = line.split(':',2) ret[:name] = records[0].strip ret[:recipient] = records[1].strip ret end def to_line(record) 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}" end end end ���������������������������������������������puppet-3.8.5/lib/puppet/provider/maillist/����������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020504� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/maillist/mailman.rb������������������������������������������������0000664�0052762�0001160�00000005446�12650174557�022461� 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-3.8.5/lib/puppet/provider/mcx/���������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017455� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/mcx/mcxcontent.rb��������������������������������������������������0000664�0052762�0001160�00000010413�12650174557�022164� 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.keys.each 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 tmp = Tempfile.new('puppet_mcx') 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, "Coult not parse ds_type from resource name '#{name}'. Specify with ds_type parameter." end # De-pluralize and downcase. ds_type = ds_type.chop.downcase.to_sym unless TypeMap.key? ds_type raise MCXContentProviderException, "Coult not parse ds_type from resource name '#{name}'. Specify with ds_type parameter." 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." 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-3.8.5/lib/puppet/provider/mount.rb�����������������������������������������������������������0000664�0052762�0001160�00000003172�12650174557�020361� 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 info "Remounting" if resource[:remounts] == :true mountcmd "-o", "remount", resource[:name] elsif ["FreeBSD", "DragonFly", "OpenBSD"].include?(Facter.value(:operatingsystem)) if self.options && !self.options.empty? options = self.options + ",update" else options = "update" end mountcmd "-o", options, resource[:name] else unmount mount end 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-3.8.5/lib/puppet/provider/mount/�������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020030� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/mount/parsed.rb����������������������������������������������������0000664�0052762�0001160�00000022326�12650174557�021641� 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 preceeding 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 Puppet.err "Prefetch: Mount[#{result[:name]}]: Field 'device' is missing" end if ! result[:fstype] result[:fstype] = :absent Puppet.err "Prefetch: Mount[#{result[:name]}]: Field 'fstype' is missing" 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[#{result[:name]}]: Field 'device' must be in the format of <absolute path> or <host>:<absolute path>" 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[#{result[:name]}]: Field 'device' is required" 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[#{result[:name]}]: Field 'device' is required" 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 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" end end instances end def flush needs_mount = @property_hash.delete(:needs_mount) super mount if needs_mount end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/naginator.rb�������������������������������������������������������0000664�0052762�0001160�00000004073�12650174557�021202� 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}'" 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_type.name}: #{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-3.8.5/lib/puppet/provider/nameservice.rb�����������������������������������������������������0000664�0052762�0001160�00000017417�12650174557�021527� 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 = [] listbyname do |name| objects << new(:name => name, :ensure => :present) 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) raise Puppet::DevError, "#{name} is not a valid attribute for #{resource_type.name}" unless resource_type.valid_parameter?(name) @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 names = [] Etc.send("set#{section()}ent") begin while ent = Etc.send("get#{section()}ent") names << ent.name yield ent.name if block_given? end ensure 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}: #{@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, because DirectoryServices is stupid. 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 raise Puppet::DevError, "Invalid resource name #{resource}" 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 = [] 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) end rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not create #{@resource.class.name} #{@resource.name}: #{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) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not delete #{@resource.class.name} #{@resource.name}: #{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 = Etc.send(@etcmethod, @resource[: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 groups = [] # Reset our group list Etc.setgrent user = @resource[:name] # Now iterate across all of the groups, adding each one our # user is a member of while group = Etc.getgrent members = group.mem groups << group.name if members.include? user end # We have to close the file, so each listing is a separate # reading of the file. Etc.endgrent groups.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 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) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}", detail.backtrace end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/nameservice/�������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�021167� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/nameservice/directoryservice.rb������������������������������������0000664�0052762�0001160�00000061163�12650174557�025111� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/provider/nameservice' require 'facter/util/plist' 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 attr_writer :macosx_version_major end initvars commands :dscl => "/usr/bin/dscl" commands :dseditgroup => "/usr/sbin/dseditgroup" commands :sw_vers => "/usr/bin/sw_vers" commands :plutil => '/usr/bin/plutil' confine :operatingsystem => :darwin defaultfor :operatingsystem => :darwin # 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 managine. 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.get_macosx_version_major return @macosx_version_major if defined?(@macosx_version_major) begin product_version_major = Facter.value(:macosx_productversion_major) fail("#{product_version_major} is not supported by the directoryservice provider") if %w{10.0 10.1 10.2 10.3 10.4}.include?(product_version_major) @macosx_version_major = product_version_major return @macosx_version_major rescue Puppet::ExecutionFailure => detail fail("Could not determine OS X version: #{detail}") end end def self.list_all_present # 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_type.name} list from DirectoryService") end dscl_output.split("\n") end def self.parse_dscl_plist_data(dscl_output) Plist.parse_xml(dscl_output) end def self.generate_attribute_hash(input_hash, *type_properties) attribute_hash = {} input_hash.keys.each 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 acccounts. # 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 # (#11593) Remove support for OS X 10.4 and earlier fail_if_wrong_version dscl_plist = self.parse_dscl_plist_data(dscl_output) self.generate_attribute_hash(dscl_plist, *type_properties) end def self.fail_if_wrong_version if (Puppet::Util::Package.versioncmp(self.get_macosx_version_major, '10.5') == -1) fail("Puppet does not support OS X versions < 10.5") end 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 http://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. # (#11593) Remove support for OS X 10.4 and earlier fail_if_wrong_version command_vector = [ command(:dscl), "-plist", "." ] # JJM: The actual action to perform. See "man dscl" # Common actiosn: -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) # Use Puppet::Util::Package.versioncmp() to catch the scenario where a # version '10.10' would be < '10.7' with simple string comparison. This # if-statement only executes if the current version is less-than 10.7 if (Puppet::Util::Package.versioncmp(get_macosx_version_major, '10.7') == -1) password_hash_file = "#{password_hash_dir}/#{guid}" begin File.open(password_hash_file, 'w') { |f| f.write(password_hash)} rescue Errno::EACCES => detail fail("Could not write to password hash file: #{detail}") end # NBK: For shadow hashes, the user AuthenticationAuthority must contain a value of # ";ShadowHash;". The LKDC in 10.5 makes this more interesting though as it # will dynamically generate ;Kerberosv5;;username@LKDC:SHA1 attributes if # missing. Thus we make sure we only set ;ShadowHash; if it is missing, and # we can do this with the merge command. This allows people to continue to # use other custom AuthenticationAuthority attributes without stomping on them. # # There is a potential problem here in that we're only doing this when setting # the password, and the attribute could get modified at other times while the # hash doesn't change and so this doesn't get called at all... but # without switching all the other attributes to merge instead of create I can't # see a simple enough solution for this that doesn't modify the user record # every single time. This should be a rather rare edge case. (famous last words) dscl_vector = self.get_exec_preamble("-merge", resource_name) dscl_vector << "AuthenticationAuthority" << ";ShadowHash;" begin execute(dscl_vector) rescue Puppet::ExecutionFailure => detail fail("Could not set AuthenticationAuthority.") end else # 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 OX X, we will fail early and warn them. if password_hash.length != 136 fail("OS X 10.7 requires a Salted SHA512 hash password of 136 characters. \ Please check your password and try again.") end if Puppet::FileSystem.exist?("#{users_plist_dir}/#{resource_name}.plist") # 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 = Plist::parse_xml(plutil( '-convert', 'xml1', '-o', '/dev/stdout', \ "#{users_plist_dir}/#{resource_name}.plist")) # users_plist['ShadowHashData'][0].string 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].string converted_hash_plist = convert_binary_to_xml(password_hash_plist) else users_plist['ShadowHashData'] = [StringIO.new] converted_hash_plist = {'SALTED-SHA512' => StringIO.new} end # converted_hash_plist['SALTED-SHA512'].string 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'].string = \ 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_xml_to_binary(converted_hash_plist) users_plist['ShadowHashData'][0].string = changed_plist Plist::Emit.save_plist(users_plist, "#{users_plist_dir}/#{resource_name}.plist") plutil('-convert', 'binary1', "#{users_plist_dir}/#{resource_name}.plist") end end end def self.get_password(guid, username) # Use Puppet::Util::Package.versioncmp() to catch the scenario where a # version '10.10' would be < '10.7' with simple string comparison. This # if-statement only executes if the current version is less-than 10.7 if (Puppet::Util::Package.versioncmp(get_macosx_version_major, '10.7') == -1) password_hash = nil password_hash_file = "#{password_hash_dir}/#{guid}" if Puppet::FileSystem.exist?(password_hash_file) and File.file?(password_hash_file) fail("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 else if Puppet::FileSystem.exist?("#{users_plist_dir}/#{username}.plist") # 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 = Plist::parse_xml(plutil('-convert', 'xml1', '-o', '/dev/stdout', "#{users_plist_dir}/#{username}.plist")) if users_plist['ShadowHashData'] # users_plist['ShadowHashData'][0].string 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].string converted_hash_plist = convert_binary_to_xml(password_hash_plist) # converted_hash_plist['SALTED-SHA512'].string 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'].string.unpack("H*")[0] password_hash end end end end # This method will accept a hash that has been returned from Plist::parse_xml # and convert it to a binary plist (string value). def self.convert_xml_to_binary(plist_data) Puppet.debug('Converting XML plist to binary') Puppet.debug('Executing: \'plutil -convert binary1 -o - -\'') IO.popen('plutil -convert binary1 -o - -', 'r+') do |io| io.write plist_data.to_plist io.close_write @converted_plist = io.read end @converted_plist end # This method will accept a binary plist (as a string) and convert it to a # hash via Plist::parse_xml. def self.convert_binary_to_xml(plist_data) Puppet.debug('Converting binary plist to XML') Puppet.debug('Executing: \'plutil -convert xml1 -o - -\'') IO.popen('plutil -convert xml1 -o - -', 'r+') do |io| io.write plist_data io.close_write @converted_plist = io.read end Puppet.debug('Converting XML values to a hash.') @plist_hash = Plist::parse_xml(@converted_plist) @plist_hash 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") 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 = Plist.parse_xml(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.class.name}[#{@resource.name}]: #{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.class.name}[#{@resource.name}]: #{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.class.name} #{@resource.name}: #{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(id_type='gid') end if property == :uid and value.nil? value = self.class.next_system_id(id_type='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.class.name} #{@resource.name}: #{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 => detail # 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.name}, #{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: #{@resource.name}, #{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-3.8.5/lib/puppet/provider/nameservice/objectadd.rb�������������������������������������������0000664�0052762�0001160�00000001461�12650174557�023436� 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 modifycmd(param, value) cmd = [command(param.to_s =~ /password_.+_age/ ? :password : :modify)] cmd << flag(param) << value if @resource.allowdupe? && ((param == :uid) || (param == :gid and self.class.name == :groupadd)) cmd << "-o" end cmd << @resource[:name] cmd end def posixmethod(name) name = name.intern if name.is_a? String method = self.class.option(name, :method) || name method end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/nameservice/pw.rb��������������������������������������������������0000664�0052762�0001160�00000000622�12650174557�022143� 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-3.8.5/lib/puppet/provider/network_device.rb��������������������������������������������������0000664�0052762�0001160�00000002645�12650174557�022233� 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 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-3.8.5/lib/puppet/provider/package.rb���������������������������������������������������������0000664�0052762�0001160�00000002404�12650174557�020607� 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? @property_hash = query || {:ensure => :absent} @property_hash[:ensure] = :absent 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-3.8.5/lib/puppet/provider/package/�����������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020261� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/package/aix.rb�����������������������������������������������������0000664�0052762�0001160�00000010731�12650174557�021372� 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 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 info = updates[package[:name]] package.provider.latest_info = info[0] 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 '#{@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.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-3.8.5/lib/puppet/provider/package/appdmg.rb��������������������������������������������������0000664�0052762�0001160�00000007021�12650174557�022057� 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's 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' Puppet::Type.type(:package).provide(:appdmg, :parent => Puppet::Provider::Package) do desc "Package management which copies application bundles to a target." confine :operatingsystem => :darwin 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}" File.open("/var/db/.puppet_appdmg_installed_#{name}", "w") do |t| t.print "name: '#{name}'\n" t.print "source: '#{orig_source}'\n" end end def self.installpkgdmg(source, name) require 'open-uri' require 'facter/util/plist' 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", "-", "-k", "-L", "-s", "--url", source Puppet.debug "Success: curl transfered [#{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 = Plist::parse_xml 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 mounts.each do |fspath| Dir.entries(fspath).select { |f| f =~ /\.app$/i }.each do |pkg| installapp("#{fspath}/#{pkg}", name, source) end end 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 source = nil unless source = @resource[:source] self.fail "Mac OS X PKG DMG's must specify a package source." end unless name = @resource[:name] self.fail "Mac OS X PKG DMG's must specify a package name." end self.class.installpkgdmg(source,name) end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/package/apple.rb���������������������������������������������������0000664�0052762�0001160�00000002524�12650174557�021713� 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 builtin 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 source = nil unless source = @resource[:source] self.fail "Mac OS X packages must specify a package source" end installer "-pkg", source, "-target", "/" end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/package/apt.rb�����������������������������������������������������0000664�0052762�0001160�00000006454�12650174557�021404� 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 :operatingsystem => [:debian, :ubuntu] 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") preseed response else self.info "No responsefile specified or non existant, 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-3.8.5/lib/puppet/provider/package/aptitude.rb������������������������������������������������0000664�0052762�0001160�00000001520�12650174557�022424� 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 #{self.name}" ) end end def purge aptitude '-y', 'purge', @resource[:name] end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/package/aptrpm.rb��������������������������������������������������0000664�0052762�0001160�00000003457�12650174557�022123� 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}'" 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-3.8.5/lib/puppet/provider/package/blastwave.rb�����������������������������������������������0000664�0052762�0001160�00000005223�12650174557�022601� 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}" 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-3.8.5/lib/puppet/provider/package/dpkg.rb����������������������������������������������������0000664�0052762�0001160�00000010775�12650174557�021546� 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 #{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-3.8.5/lib/puppet/provider/package/fink.rb����������������������������������������������������0000664�0052762�0001160�00000003513�12650174557�021540� 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") preseed response else self.info "No responsefile specified or non existant, 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-3.8.5/lib/puppet/provider/package/freebsd.rb�������������������������������������������������0000664�0052762�0001160�00000002536�12650174557�022227� 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 #{@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-3.8.5/lib/puppet/provider/package/gem.rb�����������������������������������������������������0000664�0052762�0001160�00000007520�12650174557�021363� 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 used as the remote gem repository; if a 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 at all, the gem will be installed from the default gem repositories. This provider supports the `install_options` attribute, which allows 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 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 << "^" + name + "$" end begin list = execute(gem_list_command).lines. map {|set| gemsplit(set) }. reject {|x| x.nil? } rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not list gems: #{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+\((.+)\)/ name = $1 versions = $2.split(/,\s*/) { :name => name, :ensure => versions.map{|v| v.split[0]}, :provider => :gem } else Puppet.warning "Could not match #{desc}" unless desc.chomp.empty? nil end end def self.instances(justme = false) gemlist(:local => true).collect do |hash| new(hash) end end def install(useversion = true) command = [command(:gemcmd), "install"] command << "-v" << resource[:ensure] if (! resource[:ensure].is_a? Symbol) and useversion if source = resource[:source] begin uri = URI.parse(source) rescue => detail self.fail Puppet::Error, "Invalid source '#{uri}': #{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 # interpret it as a gem repository command << "--source" << "#{source}" << resource[:name] end else command << "--no-rdoc" << "--no-ri" << resource[:name] end command += install_options if resource[:install_options] output = execute(command) # Apparently some stupid gem versions don't exit non-0 on failure self.fail "Could not install: #{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 gemcmd "uninstall", "-x", "-a", resource[:name] end def update self.install(false) end def install_options join_options(resource[:install_options]) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/package/hpux.rb����������������������������������������������������0000664�0052762�0001160�00000001707�12650174557�021600� 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-3.8.5/lib/puppet/provider/package/macports.rb������������������������������������������������0000664�0052762�0001160�00000006516�12650174557�022447� 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) output = port("-q", :install, @resource[:name]) else output = 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-3.8.5/lib/puppet/provider/package/msi.rb�����������������������������������������������������0000664�0052762�0001160�00000010155�12650174557�021401� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/package' require 'puppet/util/windows' Puppet::Type.type(:package).provide(:msi, :parent => Puppet::Provider::Package) do desc "Windows package management by installing and removing MSIs. The `msi` provider is deprecated. Use the `windows` provider instead." confine :operatingsystem => :windows has_feature :installable has_feature :uninstallable has_feature :install_options has_feature :uninstall_options class MsiPackage extend Enumerable include Puppet::Util::Windows::Registry extend Puppet::Util::Windows::Registry def self.installer WIN32OLE.new("WindowsInstaller.Installer") end def self.each(&block) inst = installer inst.UILevel = 2 inst.Products.each do |guid| # products may be advertised, installed in a different user # context, etc, we only want to know about products currently # installed in our context. next unless inst.ProductState(guid) == 5 package = { :name => inst.ProductInfo(guid, 'ProductName'), # although packages have a version, the provider isn't versionable, # so we can't return a version # :ensure => inst.ProductInfo(guid, 'VersionString'), :ensure => :installed, :provider => :msi, :productcode => guid, :packagecode => inst.ProductInfo(guid, 'PackageCode') } yield package end end end def self.instances [] end def initialize(resource = nil) Puppet.deprecation_warning "The `:msi` package provider is deprecated, use the `:windows` provider instead." super(resource) end # Find first package whose PackageCode, e.g. {B2BE95D2-CD2C-46D6-8D27-35D150E58EC9}, # matches the resource name (case-insensitively due to hex) or the ProductName matches # the resource name. The ProductName is not guaranteed to be unique, but the PackageCode # should be if the package is authored correctly. def query MsiPackage.enum_for.find do |package| resource[:name].casecmp(package[:packagecode]) == 0 || resource[:name] == package[:name] end end def install fail("The source parameter is required when using the MSI provider.") unless resource[:source] # Unfortunately, we can't use the msiexec method defined earlier, # because of the special quoting we need to do around the MSI # properties to use. command = ['msiexec.exe', '/qn', '/norestart', '/i', shell_quote(resource[:source]), install_options].flatten.compact.join(' ') output = execute(command, :failonfail => false, :combine => true) check_result(output.exitstatus) end def uninstall fail("The productcode property is missing.") unless properties[:productcode] command = ['msiexec.exe', '/qn', '/norestart', '/x', properties[:productcode], uninstall_options].flatten.compact.join(' ') output = execute(command, :failonfail => false, :combine => true) check_result(output.exitstatus) end # (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' # http://msdn.microsoft.com/en-us/library/windows/desktop/aa368542(v=vs.85).aspx case hr when 0 # yeah when 1641 warning("The package #{operation}ed successfully and the system is rebooting now.") when 3010 warning("The package #{operation}ed successfully, but the system must be rebooted.") else raise Puppet::Util::Windows::Error.new("Failed to #{operation}", hr) end end def validate_source(value) fail("The source parameter cannot be empty when using the MSI provider.") if value.empty? end def install_options join_options(resource[:install_options]) end def uninstall_options join_options(resource[:uninstall_options]) end def shell_quote(value) value.include?(' ') ? %Q["#{value.gsub(/"/, '\"')}"] : value end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/package/nim.rb�����������������������������������������������������0000664�0052762�0001160�00000026455�12650174557�021406� 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}'") 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 '#{@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.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 = "Unable to find package '#{pkg}' " if (version_specified) errmsg << "with version '#{version}' " end errmsg << "on lpp_source '#{source}'" 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) # RPM's 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}'" 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}'" 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}'" 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}'" 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}'" 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-3.8.5/lib/puppet/provider/package/openbsd.rb�������������������������������������������������0000664�0052762�0001160�00000015170�12650174557�022245� 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}") 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} if output.nil? or output.size == 0 or output =~ /Error from / debug "Failed to query for #{resource[:name]}" return properties[:ensure] 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 properties[:ensure] else match = /^(.*)-(\d[^-]*)[-]?(\w*)$/.match(output) debug "Latest available for #{resource[:name]}: #{match[2]}" if properties[:ensure].to_sym == :absent return match[2] end vcmp = properties[:ensure].split('.').map{|s|s.to_i} <=> match[2].split('.').map{|s|s.to_i} if vcmp > 0 debug "ensure: #{properties[:ensure]}" # 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]} #{properties[:ensure]} newer then available #{match[2]}" return properties[:ensure] 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] } # 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. # So we always need to handle flavors ourselves as to not break installs. if latest and resource[:flavor] full_name = "#{resource[:name]}--#{resource[:flavor]}" elsif latest # Don't depend on get_version for updates. full_name = @resource[:name] else full_name = [ @resource[:name], get_version || @resource[:ensure], @resource[:flavor] ].join('-').chomp('-').chomp('-') end 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_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" 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-3.8.5/lib/puppet/provider/package/opkg.rb����������������������������������������������������0000664�0052762�0001160�00000003535�12650174557�021555� 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 %s" % 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 Dir.entries('/var/opkg-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 end �������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/package/pacman.rb��������������������������������������������������0000664�0052762�0001160�00000014043�12650174557�022050� 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." 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 Puppet::FileSystem.exist? '/usr/bin/yaourt' confine :operatingsystem => :archlinux defaultfor :operatingsystem => :archlinux has_feature :install_options has_feature :uninstall_options has_feature :upgradeable # If yaourt is installed, we can make use of it def yaourt? return Puppet::FileSystem.exist?('/usr/bin/yaourt') end # Install a package using 'pacman', or 'yaourt' if available. # Installs quietly, without confirmation or progressbar, 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 raise Puppet::ExecutionFailure.new("Could not find package %s" % self.name) end end def install_from_repo if yaourt? cmd = %w{--noconfirm} cmd += install_options if @resource[:install_options] cmd << "-S" << @resource[:name] yaourt *cmd else cmd = %w{--noconfirm --noprogressbar} cmd += install_options if @resource[:install_options] cmd << "-Sy" << @resource[:name] pacman *cmd end end private :install_from_repo def install_from_file source = @resource[:source] begin source_uri = URI.parse source rescue => detail self.fail Puppet::Error, "Invalid source '#{source}': #{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" end pacman "--noconfirm", "--noprogressbar", "-Sy" pacman "--noconfirm", "--noprogressbar", "-U", source end private :install_from_file def self.listcmd [command(:pacman), "-Q"] end # Pacman has a concept of package groups as well. # Package groups have no versions. def self.listgroupcmd [command(:pacman), "-Qg"] end # Get installed packages (pacman -Q) def self.installedpkgs packages = [] begin execpipe(listcmd()) do |process| # pacman -Q output is 'packagename version-rel' regex = %r{^(\S+)\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 %s" % line) end } end rescue Puppet::ExecutionFailure return nil end packages end # Get installed groups (pacman -Qg) def self.installedgroups packages = [] begin execpipe(listgroupcmd()) do |process| # pacman -Qg output is 'groupname packagename' # Groups need to be deduplicated groups = Set[] process.each_line { |line| groups.add(line.split[0]) } groups.each { |line| hash = { :name => line, :ensure => "1", # Groups don't have versions, so ensure => latest # will still cause a reinstall. :provider => self.name } packages << new(hash) } end rescue Puppet::ExecutionFailure return nil end packages end # Fetch the list of packages currently installed on the system. def self.instances packages = self.installedpkgs groups = self.installedgroups result = nil if (!packages && !groups) nil elsif (packages && groups) packages.concat(groups) else packages end 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 pacman "-Sy" pacman_check = true # Query the main repos first 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.yaourt? pacman_check = false # now try the AUR retry else raise end end end # Querys the pacman master list for information about the package. def query begin output = pacman("-Qi", @resource[:name]) if output =~ /Version.*:\s(.+)/ return { :ensure => $1 } end rescue Puppet::ExecutionFailure return { :ensure => :purged, :status => 'missing', :name => @resource[:name], :error => 'ok', } end nil end # Removes a package from the system. def uninstall cmd = %w{--noconfirm --noprogressbar} cmd += uninstall_options if @resource[:uninstall_options] cmd << "-R" << @resource[:name] pacman *cmd end private def install_options join_options(@resource[:install_options]) end def uninstall_options join_options(@resource[:uninstall_options]) end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/package/pip.rb�����������������������������������������������������0000664�0052762�0001160�00000007450�12650174557�021405� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Puppet package provider for Python's `pip` package management frontend. # <http://pip.openplans.org/> require 'puppet/provider/package' require 'xmlrpc/client' require 'puppet/util/http_proxy' Puppet::Type.type(:package).provide :pip, :parent => ::Puppet::Provider::Package do desc "Python packages via `pip`." has_feature :installable, :uninstallable, :upgradeable, :versionable # 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 = which(cmd) or return [] execpipe "#{pip_cmd} freeze" do |process| process.collect do |line| next unless options = parse(line) packages << new(options) end end packages end def self.cmd if Facter.value(:osfamily) == "RedHat" and Facter.value(:operatingsystemmajrelease).to_i < 7 "pip-python" else "pip" 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 # Ask the PyPI API for the latest version number. There is no local # cache of PyPI's package list so this operation will always have to # ask the web service. def latest http_proxy_host = Puppet::Util::HttpProxy.http_proxy_host http_proxy_port = Puppet::Util::HttpProxy.http_proxy_port if http_proxy_host && http_proxy_port proxy = "#{http_proxy_host}:#{http_proxy_port}" else # nil is acceptable proxy = http_proxy_host end client = XMLRPC::Client.new2("http://pypi.python.org/pypi", proxy) client.http_header_extra = {"Content-Type" => "text/xml"} client.timeout = 10 result = client.call("package_releases", @resource[:name]) result.first rescue Timeout::Error => detail raise Puppet::Error, "Timeout while contacting pypi.python.org: #{detail}", detail.backtrace 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} 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 if pathname = which(self.class.cmd) self.class.commands :pip => pathname pip *args else raise e, 'Could not locate the pip command.', e.backtrace end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/package/pkg.rb�����������������������������������������������������0000664�0052762�0001160�00000016455�12650174557�021403� 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" # http://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' def self.instances pkg(:list, '-H').split("\n").map{|l| new(parse_line(l))} end # The IFO flag field is just what it names, the first field can have ether # 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 dont 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 %s: %s[%s]' % [self.name, flags, flags[0..0]] end ).merge( case flags[1..1] when 'f' {:ensure => 'held'} when '-' {} else raise ArgumentError, 'Unknown format %s: %s[%s]' % [self.name, flags, 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 %s: %s' % [self.name, 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 # NAME (PUBLISHER) VERSION IFO (new:630e1ffc7a19) # system/core-os 0.5.11-0.169 i-- when /^(\S+) +(\S+) +(...)$/ {:name => $1, :ensure => $2}.merge ifo_flag($3) # x11/wm/fvwm (fvwm.org) 2.6.1-3 i-- when /^(\S+) \((.+)\) +(\S+) +(...)$/ {:name => $1, :publisher => $2, :ensure => $3}.merge ifo_flag($4) # NAME (PUBLISHER) VERSION STATE UFOXI (dvd:052adf36c3f4) # SUNWcs 0.5.11-0.126 installed ----- when /^(\S+) +(\S+) +(\S+) +(.....)$/ {:name => $1, :ensure => $2}.merge pkg_state($3).merge(ufoxi_flag($4)) # web/firefox/plugin/flash (extra) 10.0.32.18-0.111 installed ----- when /^(\S+) \((.+)\) +(\S+) +(\S+) +(.....)$/ {:name => $1, :publisher => $2, :ensure => $3}.merge pkg_state($4).merge(ufoxi_flag($5)) else raise ArgumentError, 'Unknown line format %s: %s' % [self.name, 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 #{r[:out]}" unless [0,4].include? r[:exit] 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 lines = pkg(:list, "-Hn", @resource[:name]).split("\n") # remove certificate expiration warnings from the output, but report them # Note: we'd like to use select! here to modify the lines array and avoid # the second select further down. But Solaris 11 comes with ruby 1.8.7 # which doesn't support select!, so do this as two selects. cert_warnings = lines.select { |line| line =~ /^Certificate/ } if cert_warnings Puppet.warning("pkg warning: #{cert_warnings}") 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 unless should.is_a? Symbol name += "@#{should}" is = self.query unless is[:ensure].to_sym == :absent self.uninstall if Puppet::Util::Package.versioncmp(should, is[:ensure]) < 0 end end r = exec_cmd(command(:pkg), 'install', '--accept', name) return r if nofail raise Puppet::Error, "Unable to update #{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 #{r[:out]}" end # list a specific package def query r = exec_cmd(command(:pkg), 'list', '-H', @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-3.8.5/lib/puppet/provider/package/pkgdmg.rb��������������������������������������������������0000664�0052762�0001160�00000013330�12650174557�022060� 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 'facter/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 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. File.open("/var/db/.puppet_pkgdmg_installed_#{name}", "w") do |t| t.print "name: '#{name}'\n" t.print "source: '#{orig_source}'\n" end end def self.installpkgdmg(source, name) http_proxy_host = Puppet::Util::HttpProxy.http_proxy_host http_proxy_port = Puppet::Util::HttpProxy.http_proxy_port unless source =~ /\.dmg$/i || source =~ /\.pkg$/i raise Puppet::Error.new("Mac OS X PKG DMG's 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", "-", "-k", "-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 transfered [#{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 = Plist::parse_xml(xml_str) raise Puppet::Error.new("No disk entities returned by mount at #{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 source = nil unless source = @resource[:source] raise Puppet::Error.new("Mac OS X PKG DMG's must specify a package source.") end unless name = @resource[:name] raise Puppet::Error.new("Mac OS X PKG DMG's must specify a package name.") end self.class.installpkgdmg(source,name) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/package/pkgin.rb���������������������������������������������������0000664�0052762�0001160�00000004132�12650174557�021717� 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 => [ :dragonfly , :smartos ] 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 # Withouth -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 #{@resource.file}:#{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{ |package| package[:status] == '<' } return properties[:ensure] if not package return package[:ensure] end def update pkgin("-y", :install, resource[:name]) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/package/pkgutil.rb�������������������������������������������������0000664�0052762�0001160�00000012010�12650174557�022260� 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 %s" % 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 managament 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: %s" % 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 %s" % 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-3.8.5/lib/puppet/provider/package/portage.rb�������������������������������������������������0000664�0052762�0001160�00000010425�12650174557�022252� 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." has_feature :versionable { :emerge => "/usr/bin/emerge", :eix => "/usr/bin/eix", :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 version_format = self.eix_version_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 :LASTVERSION => version_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) name = package_name unless should == :present or should == :latest # We must install a specific version name = "=#{name}-#{should}" end emerge name end # The common package name format. def package_name @resource[:category] ? "#{@resource[:category]}/#{@resource[:name]}" : @resource[:name] end def uninstall emerge "--unmerge", package_name end def update self.install end def query result_format = self.class.eix_result_format result_fields = self.class.eix_result_fields version_format = self.class.eix_version_format search_field = package_name.count('/') > 0 ? "--category-name" : "--name" search_value = package_name 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 :LASTVERSION => version_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 package[:ensure] = package[:ensure] ? package[:ensure] : :absent packages << package end end case packages.size when 0 not_found_value = "#{@resource[:category] ? @resource[:category] : "<unspecified category>"}/#{@resource[:name]}" raise Puppet::Error.new("No package found with the specified name [#{not_found_value}]") when 1 return packages[0] else raise Puppet::Error.new("More than one package with the specified name [#{search_value}], please use the category parameter to disambiguate") end rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new(detail) end end def latest self.query[:version_available] end private def self.eix_search_format "'<category> <name> [<installedversions:LASTVERSION>] [<bestversion:LASTVERSION>] <homepage> <description>'" end def self.eix_result_format /^(\S+)\s+(\S+)\s+\[(\S*)\]\s+\[(\S*)\]\s+(\S+)\s+(.*)$/ end def self.eix_result_fields [:category, :name, :ensure, :version_available, :vendor, :description] end def self.eix_version_format "{last}<version>{}" end def self.eix_search_arguments ["--nocolor", "--pure-packages", "--format",self.eix_search_format] end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/package/ports.rb���������������������������������������������������0000664�0052762�0001160�00000004340�12650174557�021757� 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" defaultfor :operatingsystem => :freebsd # I hate ports %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 #{@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}'" 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}'" 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-3.8.5/lib/puppet/provider/package/portupgrade.rb���������������������������������������������0000664�0052762�0001160�00000017176�12650174557�023157� 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, $!) return nil 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 #{@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 #{@resource[:name]}" else # Any other error (dump output to log) raise Puppet::ExecutionFailure, "Unexpected output from portversion: #{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}" return nil 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-3.8.5/lib/puppet/provider/package/rpm.rb�����������������������������������������������������0000664�0052762�0001160�00000013705�12650174557�021413� 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] 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(execfail(cmd, Puppet::Error)) h[:ensure] end def install unless source = @resource[:source] @resource.fail "RPMs must specify a package source" end # RPM gets pissy if you try to install an already # installed package if @resource.should(:ensure) == @property_hash[:ensure] or @resource.should(:ensure) == :latest && @property_hash[:ensure] == latest return end flag = ["-i"] flag = ["-U", "--oldpackage"] if @property_hash[:ensure] and @property_hash[:ensure] != :absent 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 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]}" else Puppet.debug("Failed to match rpm line #{line}") end return hash end end �����������������������������������������������������������puppet-3.8.5/lib/puppet/provider/package/rug.rb�����������������������������������������������������0000664�0052762�0001160�00000002407�12650174557�021407� 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" defaultfor :operatingsystem => [:suse, :sles] 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 #{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-3.8.5/lib/puppet/provider/package/sun.rb�����������������������������������������������������0000664�0052762�0001160�00000006724�12650174557�021425� 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 eventhough pkginfo passed, we have to check the actual output raise Puppet::Error, "Unable to get information about package #{@resource[:name]} because of: #{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 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-3.8.5/lib/puppet/provider/package/sunfreeware.rb���������������������������������������������0000664�0052762�0001160�00000000633�12650174557�023137� 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-3.8.5/lib/puppet/provider/package/up2date.rb�������������������������������������������������0000664�0052762�0001160�00000002002�12650174557�022145� 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 #{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-3.8.5/lib/puppet/provider/package/urpmi.rb���������������������������������������������������0000664�0052762�0001160�00000002705�12650174557�021747� 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 #{self.name} was not present after trying to install it" 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-3.8.5/lib/puppet/provider/package/windows.rb�������������������������������������������������0000664�0052762�0001160�00000007516�12650174557�022312� 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) check_result(output.exitstatus) end def uninstall command = [package.uninstall_command, uninstall_options].flatten.compact.join(' ') output = execute(command, :failonfail => false, :combine => true) check_result(output.exitstatus) end # http://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.") when self.class::ERROR_SUCCESS_REBOOT_REQUIRED warning("The package #{operation}ed successfully, but the system must be rebooted.") else raise Puppet::Util::Windows::Error.new("Failed to #{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-3.8.5/lib/puppet/provider/package/windows/���������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�021753� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/package/windows/exe_package.rb�������������������������������������0000664�0052762�0001160�00000004626�12650174557�024545� 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 # Return an instance of the package from the registry, or nil def self.from_registry(name, values) if valid?(name, values) ExePackage.new( values['DisplayName'], 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 !!(values['DisplayName'] and values['DisplayName'].length > 0 and values['UninstallString'] and values['UninstallString'].length > 0 and values['SystemComponent'] != 1 and # DWORD values['WindowsInstaller'] != 1 and # DWORD name !~ /^KB[0-9]{6}/ and values['ParentKeyName'] == nil and values['Security Update'] == nil and values['Update Rollup'] == nil and 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) ['cmd.exe', '/c', 'start', '"puppet-install"', '/w', munge(resource[:source])] end def uninstall_command # 1. Launch using cmd /c start because if the executable is a console # application Windows will automatically display its console window # 2. Specify a quoted title, otherwise if uninstall_string is quoted, # start will interpret that to be the title, and get confused # 3. Specify /w (wait) to wait for uninstall to finish command = ['cmd.exe', '/c', 'start', '"puppet-uninstall"', '/w'] # 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-3.8.5/lib/puppet/provider/package/windows/msi_package.rb�������������������������������������0000664�0052762�0001160�00000004001�12650174557�024537� 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 # 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(values['DisplayName'], 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 !!(values['DisplayName'] and values['DisplayName'].length > 0 and values['SystemComponent'] != 1 and # DWORD values['WindowsInstaller'] == 1 and # 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-3.8.5/lib/puppet/provider/package/windows/package.rb�����������������������������������������0000664�0052762�0001160�00000005343�12650174557�023701� 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 # 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| uninstall.each_key do |name, wtime| open(hive, "#{uninstall.keyname}\\#{name}", mode) do |key| yield key, values(key) 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: '#{resource[:source]}'") unless Puppet::FileSystem.exist?(resource[:source]) ExePackage else fail("Don't know how to install '#{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 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-3.8.5/lib/puppet/provider/package/yum.rb�����������������������������������������������������0000664�0052762�0001160�00000014606�12650174557�021430� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/package' 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 :yum => "yum", :rpm => "rpm", :python => "python" self::YUMHELPER = File::join(File::dirname(__FILE__), "yumhelper.py") 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 enablerepo [Array<String>] A list of repositories to enable for this query # @param disablerepo [Array<String>] A list of repositories to disable for this query # @return [Hash<Symbol, String>] def self.latest_package_version(package, enablerepo, disablerepo) key = [enablerepo, disablerepo] @latest_versions ||= {} if @latest_versions[key].nil? @latest_versions[key] = fetch_latest_versions(enablerepo, disablerepo) 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 enablerepo [Array<String>] A list of repositories to enable for this query # @param disablerepo [Array<String>] A list of repositories 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.fetch_latest_versions(enablerepo, disablerepo) latest_versions = Hash.new {|h, k| h[k] = []} args = [self::YUMHELPER] args.concat(enablerepo.map { |repo| ['-e', repo] }.flatten) args.concat(disablerepo.map { |repo| ['-d', repo] }.flatten) python(args).scan(/^_pkg (.*)$/) do |match| hash = nevra_to_hash(match[0]) # 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]}" latest_versions[short_name] << hash latest_versions[long_name] << hash end latest_versions end def self.clear @latest_versions = nil end def install wanted = @resource[:name] # If not allowing virtual packages, do a query to ensure a real package exists unless @resource.allow_virtual? yum *['-d', '0', '-e', '0', '-y', install_options, :list, wanted].compact end should = @resource.should(:ensure) self.debug "Ensuring => #{should}" operation = :install case should when true, false, Symbol # pass should = nil else # Add the package version wanted += "-#{should}" is = self.query if is && Puppet::Util::Package.versioncmp(should, is[:ensure]) < 0 self.debug "Downgrading package #{@resource[:name]} from version #{is[:ensure]} to #{should}" operation = :downgrade end end args = ["-d", "0", "-e", "0", "-y", install_options, operation, wanted].compact yum *args # 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 #{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] ? raise Puppet::Error, "Failed to update to version #{should}, got version #{is[:ensure]} instead" if should != is[:ensure] end end # What's the latest package version available? def latest upd = self.class.latest_package_version(@resource[:name], enablerepo, disablerepo) 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 raise Puppet::DevError, "Tried to get latest on a missing package" if properties[:ensure] == :absent return properties[:ensure] end end def update # Install in yum can be used for update, too self.install end def purge yum "-y", :erase, @resource[:name] end # @deprecated def latest_info Puppet.deprecation_warning("#{self.class}##{__method__} is deprecated and is no longer used.") @latest_info end # @deprecated def latest_info=(latest) Puppet.deprecation_warning("#{self.class}##{__method__} is deprecated and is no longer used.") @latest_info = latest end private def enablerepo scan_options(resource[:install_options], '--enablerepo') end def disablerepo scan_options(resource[:install_options], '--disablerepo') 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-3.8.5/lib/puppet/provider/package/yumhelper.py�����������������������������������������������0000664�0052762�0001160�00000010627�12650174557�022654� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Python helper script to query for the packages that have # pending updates. Called by the yum package provider # # (C) 2007 Red Hat Inc. # David Lutterkort <dlutter @redhat.com> import sys import string import re # this maintains compatibility with really old platforms with python 1.x from os import popen, WEXITSTATUS # Try to use the yum libraries by default, but shell out to the yum executable # if they are not present (i.e. yum <= 2.0). This is only required for RHEL3 # and earlier that do not support later versions of Yum. Once RHEL3 is EOL, # shell_out() and related code can be removed. try: import yum except ImportError: useyumlib = 0 else: useyumlib = 1 OVERRIDE_OPTS = { 'debuglevel': 0, 'errorlevel': 0, 'logfile': '/dev/null' } enable = [] disable = [] args = list(sys.argv) while len(args) > 0: a = args.pop(0) if a == "-d": if len(args) < 1: raise ValueError, a next = args.pop(0) disable.extend( next.split(",") ) if a == "-e": if len(args) < 1: raise ValueError, a next = args.pop(0) enable.extend( next.split(",") ) def pkg_lists(my): my.doConfigSetup() for k in OVERRIDE_OPTS.keys(): if hasattr(my.conf, k): setattr(my.conf, k, OVERRIDE_OPTS[k]) else: my.conf.setConfigOption(k, OVERRIDE_OPTS[k]) my.doTsSetup() my.doRpmDBSetup() # Yum 2.2/2.3 python libraries require a couple of extra function calls to setup package sacks. # They also don't have a __version__ attribute try: yumver = yum.__version__ except AttributeError: my.doRepoSetup() my.doSackSetup() return my.doPackageLists('updates') def shell_out(): try: repostring = "" if disable: repostring += " '--disablerepo=%s'" % ",".join(disable) if enable: repostring += " '--disablerepo=%s'" % ",".join(enable) p = popen("/usr/bin/env yum%s check-update 2>&1" % repostring) output = p.readlines() rc = p.close() if rc is not None: # None represents exit code of 0, otherwise the exit code is in the # format returned by wait(). Exit code of 100 from yum represents # updates available. if WEXITSTATUS(rc) != 100: return WEXITSTATUS(rc) else: # Exit code is None (0), no updates waiting so don't both parsing output return 0 # Yum prints a line of hyphens (old versions) or a blank line between # headers and package data, so skip everything before them skipheaders = 0 for line in output: if not skipheaders: if re.compile("^((-){80}|)$").search(line): skipheaders = 1 continue # Skip any blank lines if re.compile("^[ \t]*$").search(line): continue # Format is: # Yum 1.x: name arch (epoch:)?version # Yum 2.0: name arch (epoch:)?version repo # epoch is optional if 0 p = string.split(line) pname = p[0] parch = p[1] pevr = p[2] # Separate out epoch:version-release evr_re = re.compile("^(\d:)?(\S+)-(\S+)$") evr = evr_re.match(pevr) pepoch = "" if evr.group(1) is None: pepoch = "0" else: pepoch = evr.group(1).replace(":", "") pversion = evr.group(2) prelease = evr.group(3) print "_pkg", pname, pepoch, pversion, prelease, parch return 0 except: print sys.exc_info()[0] return 1 if useyumlib: try: try: my = yum.YumBase() for repo in disable: my.repos.disableRepo(repo) for repo in enable: my.repos.enableRepo(repo) ypl = pkg_lists(my) for pkg in ypl.updates: print "_pkg %s %s %s %s %s" % (pkg.name, pkg.epoch, pkg.version, pkg.release, pkg.arch) finally: my.closeRpmDB() except IOError, e: print "_err IOError %d %s" % (e.errno, e) sys.exit(1) except AttributeError, e: # catch yumlib errors in buggy 2.x versions of yum print "_err AttributeError %s" % e sys.exit(1) else: rc = shell_out() sys.exit(rc) ���������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/package/zypper.rb��������������������������������������������������0000664�0052762�0001160�00000006032�12650174557�022141� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:package).provide :zypper, :parent => :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" confine :operatingsystem => [:suse, :sles, :sled, :opensuse] #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 #{self.name}" ) end end # What's the latest package version available? def latest #zypper can only get a list of *all* available packages? output = zypper "list-updates" if output =~ /#{Regexp.escape @resource[:name]}\s*\|.*?\|\s*([^\s\|]+)/ return $1 else # zypper didn't find updates, pretend the current # version is the latest return @property_hash[:ensure] end end def update # zypper install can be used for update, too self.install end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/parsedfile.rb������������������������������������������������������0000664�0052762�0001160�00000033672�12650174557�021345� 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. class Puppet::Provider::ParsedFile < Puppet::Provider extend Puppet::Util::FileParsing class << self attr_accessor :default_target, :target 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}" 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) 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 # erronously 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 repititions 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 = {} @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 puts detail.backtrace if Puppet[:trace] Puppet.err "Could not prefetch #{self.resource_type.name} provider '#{self.name}' target '#{target}': #{detail}. Treating as empty" 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 #{self.name} returned nil" 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-3.8.5/lib/puppet/provider/scheduled_task/����������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�021650� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb������������������������������0000664�0052762�0001160�00000045453�12650174557�026074� 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] 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 = day.to_f and 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-3.8.5/lib/puppet/provider/selboolean/��������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�021011� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/selboolean/getsetsebool.rb�����������������������������������������0000664�0052762�0001160�00000002366�12650174557�024045� 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-3.8.5/lib/puppet/provider/selmodule/���������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020657� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/selmodule/semodule.rb����������������������������������������������0000664�0052762�0001160�00000006421�12650174557�023025� 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 filename = selmod_name_to_filename mod = File.new(filename, "r") (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) self.debug "file version #{v}" v end def selmodversion_loaded lines = () begin execpipe("#{command(:semodule)} --list") do |output| output.each_line do |line| line.chomp! 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: #{lines.join(' ').chomp!}", $!.backtrace end nil end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/service/�����������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020326� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/service/base.rb����������������������������������������������������0000664�0052762�0001160�00000006135�12650174557�021573� 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 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] ps = Facter["ps"].value @resource.fail "You must upgrade Facter to a version that includes 'ps'" unless ps and ps != "" regex = Regexp.new(@resource[:pattern]) self.debug "Executing '#{ps}'" IO.popen(ps) { |table| table.each_line { |line| if regex.match(line) self.debug "Process matched: #{line}" ary = line.sub(/^\s+/, '').split(/\s+/) return ary[1] end } } nil end # 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) # Expicitly calling exitstatus to facilitate testing if $CHILD_STATUS.exitstatus == 0 return :running else return :stopped end elsif pid = self.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 "#{self.name} is not running" 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-3.8.5/lib/puppet/provider/service/bsd.rb�����������������������������������������������������0000664�0052762�0001160�00000002540�12650174557�021425� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Manage FreeBSD services. Puppet::Type.type(:service).provide :bsd, :parent => :init do desc <<-EOT FreeBSD's (and probably NetBSD's?) form of `init`-style service management. Uses `rc.conf.d` for service enabling and disabling. EOT confine :operatingsystem => [:freebsd, :netbsd, :openbsd, :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, @model[: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, @model[: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, @model[:name]) open(rcfile, 'w') { |f| f << "%s_enable=\"YES\"\n" % @model[:name] } end # Override stop/start commands to use one<cmd>'s and the avoid race condition # where provider trys to stop/start the service before it is enabled def startcmd [self.initscript, :onestart] end def stopcmd [self.initscript, :onestop] end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/service/daemontools.rb���������������������������������������������0000664�0052762�0001160�00000012351�12650174557�023202� 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 overriden 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(dummy_argument=:work_arround_for_ruby_GC_bug) 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 overriden 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-3.8.5/lib/puppet/provider/service/debian.rb��������������������������������������������������0000664�0052762�0001160�00000004377�12650174557�022111� 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 # http://projects.reductivelabs.com/issues/2538 # is resolved. commands :invoke_rc => "/usr/sbin/invoke-rc.d" defaultfor :operatingsystem => :debian # 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. http://projects.reductivelabs.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 iniscript does not support query # The debian policy states that the initscript should support methods of query # For those that do not, peform 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 end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/service/freebsd.rb�������������������������������������������������0000664�0052762�0001160�00000010031�12650174557�022261� 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, /# (.*)/, '\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-3.8.5/lib/puppet/provider/service/gentoo.rb��������������������������������������������������0000664�0052762�0001160�00000002246�12650174557�022153� 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-3.8.5/lib/puppet/provider/service/init.rb����������������������������������������������������0000664�0052762�0001160�00000013455�12650174557�021627� 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" else "/etc/init.d" end 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 suddently 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 adddressed 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 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} 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 scriptwith '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 none", 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-3.8.5/lib/puppet/provider/service/launchd.rb�������������������������������������������������0000664�0052762�0001160�00000031531�12650174557�022275� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'facter/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 `launchd` documentation, see: * <http://developer.apple.com/macosx/launchd.html> * <http://launchd.macosforge.org/> 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' or 'status'. EOT include Puppet::Util::Warnings commands :launchctl => "/bin/launchctl" commands :sw_vers => "/usr/bin/sw_vers" commands :plutil => "/usr/bin/plutil" defaultfor :operatingsystem => :darwin confine :operatingsystem => :darwin 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| job = read_plist(filepath) next if job.nil? if job.has_key?("Label") @label_to_path_map[job["Label"]] = filepath else Puppet.warning("The #{filepath} plist does not contain a 'label' key; " + "Puppet is skipping it") 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 # Launchd implemented plist overrides in version 10.6. # This method checks the major_version of OS X and returns true if # it is 10.6 or greater. This allows us to implement different plist # behavior for versions >= 10.6 def has_macosx_plist_overrides? # (#11593) Remove support for OS X 10.4 & earlier # leaving this as is because 10.5 still didn't have plist support return self.class.get_os_version > 9 end # Read a plist, whether its format is XML or in Apple's "binary1" # format. def self.read_plist(path) begin return Plist::parse_xml(path) rescue ArgumentError => detail Puppet.debug("Error reading #{path}: #{detail}. Retrying with plutil.") end begin Plist::parse_xml(plutil('-convert', 'xml1', '-o', '/dev/stdout', path)) rescue Puppet::ExecutionFailure => detail Puppet.warning("Cannot read file #{path}; Puppet is skipping it. \n" + "Details: #{detail}") return nil end 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 def self.get_macosx_version_major return @macosx_version_major if @macosx_version_major begin product_version_major = Facter.value(:macosx_productversion_major) fail("#{product_version_major} is not supported by the launchd provider") if %w{10.0 10.1 10.2 10.3 10.4}.include?(product_version_major) @macosx_version_major = product_version_major return @macosx_version_major rescue Puppet::ExecutionFailure => detail self.fail Puppet::Error, "Could not determine OS X version: #{detail}", detail end 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 # 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, job_plist = 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, job_plist = 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_path, job_plist = plist_from_label(resource[:name]) job_plist_disabled = job_plist["Disabled"] if job_plist.has_key?("Disabled") if has_macosx_plist_overrides? if FileTest.file?(self.class.launchd_overrides) and overrides = self.class.read_plist(self.class.launchd_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 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. # Starting in 10.6 we need to write out a disabled key to the global # overrides plist, in earlier versions this is stored in the job plist itself. def enable if has_macosx_plist_overrides? overrides = self.class.read_plist(self.class.launchd_overrides) if self.class.get_os_version < 14 overrides[resource[:name]] = { "Disabled" => false } else overrides[resource[:name]] = false end Plist::Emit.save_plist(overrides, self.class.launchd_overrides) else job_path, job_plist = plist_from_label(resource[:name]) if self.enabled? == :false job_plist.delete("Disabled") Plist::Emit.save_plist(job_plist, job_path) end end end def disable if has_macosx_plist_overrides? overrides = self.class.read_plist(self.class.launchd_overrides) if self.class.get_os_version < 14 overrides[resource[:name]] = { "Disabled" => true } else overrides[resource[:name]] = true end Plist::Emit.save_plist(overrides, self.class.launchd_overrides) else job_path, job_plist = plist_from_label(resource[:name]) job_plist["Disabled"] = true Plist::Emit.save_plist(job_plist, job_path) end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/service/openbsd.rb�������������������������������������������������0000664�0052762�0001160�00000020261�12650174557�022307� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:service).provide :openbsd, :parent => :init do desc "Provider for OpenBSD's rc.d daemon control scripts" confine :operatingsystem => :openbsd defaultfor :operatingsystem => :openbsd has_feature :flaggable def self.rcconf() '/etc/rc.conf' end def self.rcconf_local() '/etc/rc.conf.local' end def self.defpath ["/etc/rc.d"] end def startcmd [self.initscript, "-f", :start] end def restartcmd (@resource[:hasrestart] == :true) && [self.initscript, "-f", :restart] end def statuscmd [self.initscript, :check] end # Fetch the state of all service resources def self.prefetch(resources) services = instances resources.keys.each do |name| if provider = services.find { |svc| svc.name == name } resources[name].provider = provider end end end # Return the list of rc scripts # @api private def self.rclist unless @rclist Puppet.debug "Building list of rc scripts" @rclist = [] defpath.each do |p| Dir.glob(p + '/*').each do |item| @rclist << item if File.executable?(item) end end end @rclist end # Return a hash where the keys are the rc script names as symbols with flags # as values # @api private def self.rcflags unless @flag_hash Puppet.debug "Retrieving flags for all discovered services" Puppet.debug "Reading the contents of the rc conf files" if File.exists?(rcconf_local()) rcconf_local_contents = File.readlines(rcconf_local()) else rcconf_local_contents = [] end if File.exists?(rcconf()) rcconf_contents = File.readlines(rcconf()) else rcconf_contents = [] end @flag_hash = {} rclist().each do |rcitem| rcname = rcitem.split('/').last if flagline = rcconf_local_contents.find {|l| l =~ /^#{rcname}_flags/ } flag = parse_rc_line(flagline) @flag_hash[rcname.to_sym] ||= flag end # For the defaults, if the flags are set to 'NO', we skip setting the # flag here, since it will already be disabled, and this makes the # output of `puppet resource service` a bit more correct. if flagline = rcconf_contents.find {|l| l =~ /^#{rcname}_flags/ } flag = parse_rc_line(flagline) unless flag == "NO" @flag_hash[rcname.to_sym] ||= flag end end @flag_hash[rcname.to_sym] ||= nil end end @flag_hash end # @api private def self.parse_rc_line(rc_line) rc_line.sub!(/\s*#(.*)$/,'') regex = /\w+_flags=(.*)/ rc_line.match(regex)[1].gsub(/^"/,'').gsub(/"$/,'') end # Read the rc.conf* files and determine the value of the flags # @api private def self.get_flags(rcname) rcflags() @flag_hash[rcname.to_sym] end def self.instances instances = [] defpath.each do |path| unless File.directory?(path) Puppet.debug "Service path #{path} does not exist" next end rclist().each do |d| instances << new( :name => File.basename(d), :path => path, :flags => get_flags(File.basename(d)), :hasstatus => true ) end end instances end # @api private def rcvar_name self.name + '_flags' end # @api private def read_rcconf_local_text() if File.exists?(self.class.rcconf_local()) File.read(self.class.rcconf_local()) else [] end end # @api private def load_rcconf_local_array if File.exists?(self.class.rcconf_local()) File.readlines(self.class.rcconf_local()).map {|l| l.chomp! } else [] end end # @api private def write_rc_contents(file, text) Puppet::Util.replace_file(file, 0644) do |f| f.write(text) end end # @api private def set_content_flags(content,flags) unless content.is_a? Array debug "content must be an array at flags" return "" else content.reject! {|l| l.nil? } end if flags.nil? or flags.size == 0 if in_base? append = resource[:name] + '_flags=""' end else append = resource[:name] + '_flags="' + flags + '"' end if content.find {|l| l =~ /#{resource[:name]}_flags/ }.nil? content << append else content.map {|l| l.gsub!(/^#{resource[:name]}_flags="(.*)?"(.*)?$/, append) } end content end # @api private def remove_content_flags(content) content.reject {|l| l =~ /#{resource[:name]}_flags/ } end # return an array of the currently enabled pkg_scripts # @api private def pkg_scripts current = load_rcconf_local_array() if scripts = current.find{|l| l =~ /^pkg_scripts/ } if match = scripts.match(/^pkg_scripts="(.*)?"(.*)?$/) match[1].split(' ') else [] end else [] end end # return the array with the current resource added # @api private def pkg_scripts_append [pkg_scripts(), resource[:name]].flatten.uniq end # return the array without the current resource # @api private def pkg_scripts_remove pkg_scripts().reject {|s| s == resource[:name] } end # Modify the content array to contain the requsted pkg_scripts line and retun # the resulting array # @api private def set_content_scripts(content,scripts) unless content.is_a? Array debug "content must be an array at scripts" return "" else content.reject! {|l| l.nil? } end scripts_line = 'pkg_scripts="' + scripts.join(' ') + '"' if content.find {|l| l =~ /^pkg_scripts/ }.nil? content << scripts_line else # Replace the found pkg_scripts line with our own content.each_with_index {|l,i| if l =~ /^pkg_scripts/ content[i] = scripts_line end } end content end # Determine if the rc script is included in base # @api private def in_base? script = File.readlines(self.class.rcconf).find {|s| s =~ /^#{rcvar_name}/ } !script.nil? end # @api private def default_disabled? line = File.readlines(self.class.rcconf).find {|l| l =~ /#{rcvar_name}/ } self.class.parse_rc_line(line) == 'NO' end def enabled? if in_base? if (@property_hash[:flags].nil? or @property_hash[:flags] == 'NO') :false else :true end else if (pkg_scripts().include?(@property_hash[:name])) :true else :false end end end def enable self.debug("Enabling #{self.name}") end # We should also check for default state def disable self.debug("Disabling #{self.name}") end def flags @property_hash[:flags] end def flags=(value) @property_hash[:flags] = value end def flush debug "Flusing resource for #{self.name}" # Here we load the contents of the rc.conf.local file into the contents # variable, modify it if needed, and then compare that to the original. If # they are different, we write it out. original = load_rcconf_local_array() content = original debug @property_hash.inspect if resource[:enable] == :true #set_flags(resource[:flags]) content = set_content_flags(content, resource[:flags]) # We need only add append the resource name to the pkg_scripts if the # package is not found in the base system. if not in_base? content = set_content_scripts(content,pkg_scripts_append()) end elsif resource[:enable] == :false # By virtue of being excluded from the base system, all packages are # disabled by default and need not be set in the rc.conf.local at all. if not in_base? content = remove_content_flags(content) content = set_content_scripts(content,pkg_scripts_remove()) else if default_disabled? content = remove_content_flags(content) else content = set_content_flags(content, "NO") end end end # Make sure to append a newline to the end of the file unless content[-1] == "" content << "" end output = content.join("\n") # Write the contents only if necessary, and only once write_rc_contents(self.class.rcconf_local(), output) end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/service/openrc.rb��������������������������������������������������0000664�0052762�0001160�00000003332�12650174557�022143� 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-3.8.5/lib/puppet/provider/service/openwrt.rb�������������������������������������������������0000664�0052762�0001160�00000001471�12650174557�022355� 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-3.8.5/lib/puppet/provider/service/redhat.rb��������������������������������������������������0000664�0052762�0001160�00000004515�12650174557�022130� 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, :suse] # 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' to see if it is enabled return :false unless Facter.value(:osfamily) != 'Suse' || output =~ /^#{name}\s+on$/ :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-3.8.5/lib/puppet/provider/service/runit.rb���������������������������������������������������0000664�0052762�0001160�00000005471�12650174557�022024� 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 overriden 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(dummy_argument=:work_arround_for_ruby_GC_bug) 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 Puppet.info "Waiting 5 seconds for runsvdir to discover 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-3.8.5/lib/puppet/provider/service/service.rb�������������������������������������������������0000664�0052762�0001160�00000002052�12650174557�022313� 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-3.8.5/lib/puppet/provider/service/smf.rb�����������������������������������������������������0000664�0052762�0001160�00000005613�12650174557�021446� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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" def setupservice if resource[:manifest] [command(:svcs), "-l", @resource[:name]] if $CHILD_STATUS.exitstatus == 1 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.split("\n").select{|l| l !~ /^legacy_run/ }.collect do |line| state,stime,fmri = line.split(/\s+/) status = case state when /online/; :running when /maintenance/; :maintenance else :stopped end new({:name => fmri, :ensure => status}) end 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 [command(:adm), :restart, @resource[:name]] end def startcmd self.setupservice case self.status when :maintenance [command(:adm), :clear, @resource[:name]] else [command(:adm), :enable, "-s", @resource[:name]] end end def status if @resource[:status] super return end begin # get the current state and the next state, and if the next # state is set (i.e. not "-") use it for state comparison states = svcs("-H", "-o", "state,nstate", @resource[:name]).chomp.split state = states[1] == "-" ? states[0] : states[1] rescue Puppet::ExecutionFailure info "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 "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-3.8.5/lib/puppet/provider/service/src.rb�����������������������������������������������������0000664�0052762�0001160�00000007050�12650174557�021445� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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 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 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 end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/service/systemd.rb�������������������������������������������������0000664�0052762�0001160�00000003202�12650174557�022341� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Manage systemd services using /bin/systemctl Puppet::Type.type(:service).provide :systemd, :parent => :base do desc "Manages `systemd` services using `systemctl`." commands :systemctl => "systemctl" defaultfor :osfamily => [:archlinux] defaultfor :osfamily => :redhat, :operatingsystemmajrelease => "7" defaultfor :osfamily => :redhat, :operatingsystem => :fedora, :operatingsystemmajrelease => ["17", "18", "19", "20", "21"] def self.instances i = [] output = systemctl('list-unit-files', '--type', 'service', '--full', '--all', '--no-pager') output.scan(/^(\S+)\s+(disabled|enabled)\s*$/i).each do |m| i << new(:name => m[0]) end return i rescue Puppet::ExecutionFailure return [] end def disable output = systemctl(:disable, @resource[:name]) rescue Puppet::ExecutionFailure raise Puppet::Error, "Could not disable #{self.name}: #{output}", $!.backtrace end def enabled? begin systemctl("is-enabled", @resource[:name]) rescue Puppet::ExecutionFailure return :false end :true end def status begin systemctl("is-active", @resource[:name]) rescue Puppet::ExecutionFailure return :stopped end return :running end def enable output = systemctl("enable", @resource[:name]) rescue Puppet::ExecutionFailure raise Puppet::Error, "Could not enable #{self.name}: #{output}", $!.backtrace end def restartcmd [command(:systemctl), "restart", @resource[:name]] end def startcmd [command(:systemctl), "start", @resource[:name]] end def stopcmd [command(:systemctl), "stop", @resource[:name]] end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/service/upstart.rb�������������������������������������������������0000664�0052762�0001160�00000022273�12650174557�022364� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'semver' 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\./), ] defaultfor :operatingsystem => :ubuntu commands :start => "/sbin/start", :stop => "/sbin/stop", :restart => "/sbin/restart", :status_exec => "/sbin/status", :initctl => "/sbin/initctl" # 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 |file| file.write(text) end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/service/windows.rb�������������������������������������������������0000664�0052762�0001160�00000010631�12650174557�022347� 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 commands :net => 'net.exe' def enable w32ss = Win32::Service.configure( 'service_name' => @resource[:name], 'start_type' => Win32::Service::SERVICE_AUTO_START ) raise Puppet::Error.new("Win32 service enable of #{@resource[:name]} failed" ) if( w32ss.nil? ) rescue => detail raise Puppet::Error.new("Cannot enable #{@resource[:name]}, error was: #{detail}", detail ) end def disable w32ss = Win32::Service.configure( 'service_name' => @resource[:name], 'start_type' => Win32::Service::SERVICE_DISABLED ) raise Puppet::Error.new("Win32 service disable of #{@resource[:name]} failed" ) if( w32ss.nil? ) rescue => detail raise Puppet::Error.new("Cannot disable #{@resource[:name]}, error was: #{detail}", detail ) end def manual_start w32ss = Win32::Service.configure( 'service_name' => @resource[:name], 'start_type' => Win32::Service::SERVICE_DEMAND_START ) raise Puppet::Error.new("Win32 service manual enable of #{@resource[:name]} failed" ) if( w32ss.nil? ) rescue => detail raise Puppet::Error.new("Cannot enable #{@resource[:name]} for manual start, error was: #{detail}", detail ) end def enabled? w32ss = Win32::Service.config_info( @resource[:name] ) raise Puppet::Error.new("Win32 service query of #{@resource[:name]} failed" ) unless( !w32ss.nil? && w32ss.instance_of?( Struct::ServiceConfigInfo ) ) debug("Service #{@resource[:name]} start type is #{w32ss.start_type}") case w32ss.start_type when Win32::Service.get_start_type(Win32::Service::SERVICE_AUTO_START), Win32::Service.get_start_type(Win32::Service::SERVICE_BOOT_START), Win32::Service.get_start_type(Win32::Service::SERVICE_SYSTEM_START) :true when Win32::Service.get_start_type(Win32::Service::SERVICE_DEMAND_START) :manual when Win32::Service.get_start_type(Win32::Service::SERVICE_DISABLED) :false else raise Puppet::Error.new("Unknown start type: #{w32ss.start_type}") end rescue => detail raise Puppet::Error.new("Cannot get start type for #{@resource[:name]}, error was: #{detail}", detail ) end def start if enabled? == :false # If disabled and not managing enable, respect disabled and fail. if @resource[:enable].nil? raise Puppet::Error, "Will not start disabled service #{@resource[:name]} without managing enable. Specify 'enable => false' to override." # 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 net(:start, @resource[:name]) rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new("Cannot start #{@resource[:name]}, error was: #{detail}", detail ) end def stop net(:stop, @resource[:name]) rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new("Cannot stop #{@resource[:name]}, error was: #{detail}", detail ) end def status w32ss = Win32::Service.status( @resource[:name] ) raise Puppet::Error.new("Win32 service query of #{@resource[:name]} failed" ) unless( !w32ss.nil? && w32ss.instance_of?( Struct::ServiceStatus ) ) state = case w32ss.current_state when "stopped", "pause pending", "stop pending", "paused" then :stopped when "running", "continue pending", "start pending" then :running else raise Puppet::Error.new("Unknown service state '#{w32ss.current_state}' for service '#{@resource[:name]}'") end debug("Service #{@resource[:name]} is #{w32ss.current_state}") return state rescue => detail raise Puppet::Error.new("Cannot get status of #{@resource[:name]}, error was: #{detail}", detail ) end # returns all providers for all existing services and startup state def self.instances Win32::Service.services.collect { |s| new(:name => s.service_name) } end end �������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/ssh_authorized_key/������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�022571� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/ssh_authorized_key/parsed.rb���������������������������������������0000664�0052762�0001160�00000006510�12650174557�024377� 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 supresses 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}" Dir.mkdir(dir, dir_perm) end super File.chmod(file_perm, target) end end # parse sshv2 option strings, wich 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-3.8.5/lib/puppet/provider/sshkey/������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020174� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/sshkey/parsed.rb���������������������������������������������������0000664�0052762�0001160�00000001702�12650174557�022000� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/parsedfile' known = nil case Facter.value(:operatingsystem) when "Darwin"; known = "/etc/ssh_known_hosts" else known = "/etc/ssh/ssh_known_hosts" end Puppet::Type.type(:sshkey).provide( :parsed, :parent => Puppet::Provider::ParsedFile, :default_target => known, :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 end ��������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/user/��������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017644� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/user/aix.rb��������������������������������������������������������0000664�0052762�0001160�00000030107�12650174557�020754� 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 http://docs.puppetlabs.com/guides/provider_development.html # for more information # # Author:: Hector Rivas Gandara <keymon@gmail.com> # require 'puppet/provider/aixobject' require 'tempfile' require 'date' Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do desc "User 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/lsuser" commands :add => "/usr/bin/mkuser" commands :delete => "/usr/sbin/rmuser" commands :modify => "/usr/bin/chuser" commands :lsgroup => "/usr/sbin/lsgroup" 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 # Attribute verification (TODO) #verify :gid, "GID must be a string or int of a valid group" do |value| # value.is_a? String || value.is_a? Integer #end # #verify :groups, "Groups must be comma-separated" do |value| # value !~ /\s/ #end # User attributes to ignore from AIX output. def self.attribute_ignore ["name"] end # AIX attributes to properties mapping. # # Valid attributes to be managed by this provider. # It is a list with of hash # :aix_attr AIX command attribute name # :puppet_prop Puppet propertie name # :to Method to adapt puppet property to aix command value. Optional. # :from Method to adapt aix command value to puppet property. Optional self.attribute_mapping = [ {:aix_attr => :pgrp, :puppet_prop => :gid, :to => :gid_to_attr, :from => :gid_from_attr }, {:aix_attr => :id, :puppet_prop => :uid}, {:aix_attr => :groups, :puppet_prop => :groups}, {:aix_attr => :home, :puppet_prop => :home}, {:aix_attr => :shell, :puppet_prop => :shell}, {:aix_attr => :expires, :puppet_prop => :expiry, :to => :expiry_to_attr, :from => :expiry_from_attr }, {:aix_attr => :maxage, :puppet_prop => :password_max_age}, {:aix_attr => :minage, :puppet_prop => :password_min_age}, {:aix_attr => :attributes, :puppet_prop => :attributes}, { :aix_attr => :gecos, :puppet_prop => :comment }, ] #-------------- # Command definition # Return the IA module arguments based on the resource param ia_load_module def get_ia_module_args if @resource[:ia_load_module] ["-R", @resource[:ia_load_module].to_s] else [] end end # List groups and Ids def lsgroupscmd(value=@resource[:name]) [command(:lsgroup)] + self.get_ia_module_args + ["-a", "id", value] end def lscmd(value=@resource[:name]) [self.class.command(:list), "-c"] + self.get_ia_module_args + [ value] end def lsallcmd() lscmd("ALL") end def addcmd(extra_attrs = []) # Here we use the @resource.to_hash to get the list of provided parameters # Puppet does not call to self.<parameter>= method if it does not exists. # # It gets an extra list of arguments to add to the user. [self.class.command(:add)] + self.get_ia_module_args + self.hash2args(@resource.to_hash) + extra_attrs + [@resource[:name]] end # Get modify command. Set translate=false if no mapping must be used. # Needed for special properties like "attributes" def modifycmd(hash = property_hash) args = self.hash2args(hash) return nil if args.empty? [self.class.command(:modify)] + self.get_ia_module_args + args + [@resource[:name]] end def deletecmd [self.class.command(:delete)] + self.get_ia_module_args + [@resource[:name]] end #-------------- # We overwrite the create function to change the password after creation. def create super # Reset the password if needed self.password = @resource[:password] if @resource[:password] end def get_arguments(key, value, mapping, objectinfo) # In the case of attributes, return a list of key=vlaue if key == :attributes raise Puppet::Error, "Attributes must be a list of pairs key=value on #{@resource.class.name}[#{@resource.name}]" \ unless value and value.is_a? Hash return value.map { |k,v| k.to_s.strip + "=" + v.to_s.strip} end super(key, value, mapping, objectinfo) end # Get the groupname from its id def groupname_by_id(gid) groupname=nil execute(lsgroupscmd("ALL")).each_line { |entry| attrs = self.parse_attr_list(entry, nil) if attrs and attrs.include? :id and gid == attrs[:id].to_i groupname = entry.split(" ")[0] end } groupname end # Get the groupname from its id def groupid_by_name(groupname) attrs = self.parse_attr_list(execute(lsgroupscmd(groupname)).split("\n")[0], nil) attrs ? attrs[:id].to_i : nil end # Check that a group exists and is valid def verify_group(value) if value.is_a? Integer or value.is_a? Fixnum groupname = groupname_by_id(value) raise ArgumentError, "AIX group must be a valid existing group" unless groupname else raise ArgumentError, "AIX group must be a valid existing group" unless groupid_by_name(value) groupname = value end groupname end # The user's primary group. Can be specified numerically or by name. def gid_to_attr(value) verify_group(value) end # Get the group gid from its name def gid_from_attr(value) groupid_by_name(value) end # The expiry date for this user. Must be provided in # a zero padded YYYY-MM-DD HH:MM format def expiry_to_attr(value) # For chuser the expires parameter is a 10-character string in the MMDDhhmmyy format # that is,"%m%d%H%M%y" newdate = '0' if value.is_a? String and value!="0000-00-00" d = DateTime.parse(value, "%Y-%m-%d %H:%M") newdate = d.strftime("%m%d%H%M%y") end newdate end def expiry_from_attr(value) if value =~ /(..)(..)(..)(..)(..)/ #d= DateTime.parse("20#{$5}-#{$1}-#{$2} #{$3}:#{$4}") #expiry_date = d.strftime("%Y-%m-%d %H:%M") #expiry_date = d.strftime("%Y-%m-%d") expiry_date = "20#{$5}-#{$1}-#{$2}" else Puppet.warn("Could not convert AIX expires date '#{value}' on #{@resource.class.name}[#{@resource.name}]") \ unless value == '0' expiry_date = :absent end expiry_date end def open_security_passwd # helper method for tests File.open("/etc/security/passwd", 'r') end #-------------------------------- # Getter and Setter # When the provider is initialized, create getter/setter methods for each # property our resource type supports. # If setter or getter already defined it will not be overwritten #- **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 directly the /etc/security/passwd def password password = :absent user = @resource[:name] f = open_security_passwd # Skip to the user f.each_line { |l| break if l =~ /^#{user}:\s*$/ } if ! f.eof? f.each_line { |l| # If there is a new user stanza, stop break if l =~ /^\S*:\s*$/ # If the password= entry is found, return it, stripping trailing space if l =~ /^\s*password\s*=\s*(\S*)\s*$/ password = $1; break; end } end f.close() return password end def password=(value) user = @resource[:name] # Puppet execute does not support strings as input, only files. tmpfile = Tempfile.new('puppet_#{user}_pw') tmpfile << "#{user}:#{value}\n" tmpfile.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), get_ia_module_args, '-e', '-c', user].flatten begin output = execute(cmd, {:failonfail => false, :combine => true, :stdinfile => tmpfile.path }) # 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 #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}", detail.backtrace ensure tmpfile.delete() end end def managed_attribute_keys(hash) managed_attributes ||= @resource.original_parameters[:attributes] || hash.keys.map{|k| k.to_s} managed_attributes.map {|attr| key, value = attr.split("="); key.strip.to_sym} end def should_include?(key, managed_keys) !self.class.attribute_mapping_from.include?(key) and !self.class.attribute_ignore.include?(key) and managed_keys.include?(key) end def filter_attributes(hash) # Return only managed attributtes. managed_keys = managed_attribute_keys(hash) results = hash.select { |k,v| should_include?(k, managed_keys) }.inject({}) { |hash, array| hash[array[0]] = array[1]; hash } results end def attributes filter_attributes(getosinfo(false)) end def attributes=(attr_hash) #self.class.validate(param, value) param = :attributes cmd = modifycmd({param => filter_attributes(attr_hash)}) if cmd begin execute(cmd) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}", detail.backtrace end 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-3.8.5/lib/puppet/provider/user/directoryservice.rb�������������������������������������������0000664�0052762�0001160�00000067701�12650174557�023572� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'facter/util/plist' 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 :plutil => '/usr/bin/plutil' commands :dscacheutil => '/usr/bin/dscacheutil' # Provider confines and defaults confine :operatingsystem => :darwin 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 Plist.parse_xml(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.keys.each 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] = get_attribute_from_dscl('Users', attribute_hash[:name], '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 (Puppet::Util::Package.versioncmp(get_os_version, '10.7') == -1) attribute_hash[:password] = get_sha1(attribute_hash[:guid]) else if attribute_hash[:shadowhashdata].empty? attribute_hash[:password] = '*' else embedded_binary_plist = get_embedded_binary_plist(attribute_hash[:shadowhashdata]) if embedded_binary_plist['SALTED-SHA512'] attribute_hash[:password] = get_salted_sha512(embedded_binary_plist) else 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) end 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 ||= Plist.parse_xml(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) Plist.parse_xml(dscl '-plist', '.', 'read', "/#{path}/#{username}", keyname) end # The plist embedded in the ShadowHashData key is a binary plist. The # facter/util/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['dsAttrTypeNative:ShadowHashData'][0].delete(' ')).pack('H*') convert_binary_to_xml(embedded_binary_plist) end # This method will accept a hash that has been returned from Plist::parse_xml # and convert it to a binary plist (string value). def self.convert_xml_to_binary(plist_data) Puppet.debug('Converting XML plist to binary') Puppet.debug('Executing: \'plutil -convert binary1 -o - -\'') IO.popen('plutil -convert binary1 -o - -', 'r+') do |io| io.write Plist::Emit.dump(plist_data) io.close_write @converted_plist = io.read end @converted_plist end # This method will accept a binary plist (as a string) and convert it to a # hash via Plist::parse_xml. def self.convert_binary_to_xml(plist_data) Puppet.debug('Converting binary plist to XML') Puppet.debug('Executing: \'plutil -convert xml1 -o - -\'') IO.popen('plutil -convert xml1 -o - -', 'r+') do |io| io.write plist_data io.close_write @converted_plist = io.read end Puppet.debug('Converting XML values to a hash.') Plist::parse_xml(@converted_plist) 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'].string.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].string.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 (Puppet::Util::Package.versioncmp(self.class.get_os_version, '10.7') == -1) write_sha1_hash(value) else 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 intermittantly 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 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. Plist::parse_xml(plutil('-convert', 'xml1', '-o', '/dev/stdout', "#{users_plist_dir}/#{username}.plist")) 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].string self.class.convert_binary_to_xml(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) if users_plist.has_key?('ShadowHashData') users_plist['ShadowHashData'][0].string = binary_plist else users_plist['ShadowHashData'] = [new_stringio_object(binary_plist)] end write_users_plist_to_disk(users_plist) end # This method returns a new StringIO object. Why does it exist? # Well, StringIO objects have their own 'serial number', so when # writing rspec tests it's difficult to compare StringIO objects # due to this serial number. If this action is wrapped in its own # method, it can be mocked for easier testing. def new_stringio_object(value = '') StringIO.new(value) 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'] = new_stringio_object end shadow_hash_data['SALTED-SHA512'].string = base64_decode_string(value) binary_plist = self.class.convert_xml_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 http://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] = new_stringio_object unless shadow_hash_data['SALTED-SHA512-PBKDF2'][field] shadow_hash_data['SALTED-SHA512-PBKDF2'][field].string = 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_xml_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) Plist::Emit.save_plist(users_plist, "#{users_plist_dir}/#{@resource.name}.plist") plutil'-convert', 'binary1', "#{users_plist_dir}/#{@resource.name}.plist" end # This is a simple wrapper method for writing values to a file. def write_to_file(filename, value) 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 def write_sha1_hash(value) users_guid = self.class.get_attribute_from_dscl('Users', @resource.name, 'GeneratedUID')['dsAttrTypeStandard:GeneratedUID'][0] password_hash_file = "#{self.class.password_hash_dir}/#{users_guid}" write_to_file(password_hash_file, value) # NBK: For shadow hashes, the user AuthenticationAuthority must contain a value of # ";ShadowHash;". The LKDC in 10.5 makes this more interesting though as it # will dynamically generate ;Kerberosv5;;username@LKDC:SHA1 attributes if # missing. Thus we make sure we only set ;ShadowHash; if it is missing, and # we can do this with the merge command. This allows people to continue to # use other custom AuthenticationAuthority attributes without stomping on them. # # There is a potential problem here in that we're only doing this when setting # the password, and the attribute could get modified at other times while the # hash doesn't change and so this doesn't get called at all... but # without switching all the other attributes to merge instead of create I can't # see a simple enough solution for this that doesn't modify the user record # every single time. This should be a rather rare edge case. (famous last words) merge_attribute_with_dscl('Users', @resource.name, 'AuthenticationAuthority', ';ShadowHash;') end end ���������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/user/hpux.rb�������������������������������������������������������0000664�0052762�0001160�00000003606�12650174557�021163� 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." 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) super.insert(1,"-F") 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 end ��������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/user/ldap.rb�������������������������������������������������������0000664�0052762�0001160�00000007127�12650174557�021121� 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 [Fixnum, Bignum].include?(value.class) @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-3.8.5/lib/puppet/provider/user/pw.rb���������������������������������������������������������0000664�0052762�0001160�00000005476�12650174557�020634� 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, stdout, stderr = 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-3.8.5/lib/puppet/provider/user/user_role_add.rb����������������������������������������������0000664�0052762�0001160�00000013671�12650174557�023011� 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 :roles, :flag => "-R" options :auths, :flag => "-A" options :profiles, :flag => "-P" options :password_min_age, :flag => "-n" options :password_max_age, :flag => "-x" 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_solaris_rbac, :manages_passwords, :manages_password_age, :manages_shell #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].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 dont suppress the empty fields collect { |l| l.chomp.split(':', -1) }. find { |user, _| user == @resource[:name] } end def password shadow_entry[1] if shadow_entry end def password_min_age 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 # 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-3.8.5/lib/puppet/provider/user/useradd.rb����������������������������������������������������0000664�0052762�0001160�00000014565�12650174557�021634� 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, :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" 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_manage_expiry cmd = [] if @resource[:expiry] and not @resource.forcelocal? cmd << "-e #{@resource[:expiry]}" 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.to_s =~ /password_.+_age/ 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 deletecmd cmd = [command(:delete)] cmd += @resource.managehome? ? ['-r'] : [] cmd << @resource[:name] end def passcmd age_limits = [:password_min_age, :password_max_age].select { |property| @resource.should(property) } if age_limits.empty? nil else [command(:password),age_limits.collect { |property| [flag(property), @resource.should(property)]}, @resource[:name]].flatten end end [:expiry, :password_min_age, :password_max_age, :password].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) 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 end �������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/user/windows_adsi.rb�����������������������������������������������0000664�0052762�0001160�00000004313�12650174557�022665� 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 user.groups.join(',') end def groups=(groups) user.set_groups(groups, @resource[:membership] == :minimum) 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 user.password_is?( @resource[:password] ) ? @resource[:password] : :absent end def password=(value) user.password = value 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-3.8.5/lib/puppet/provider/vlan/��������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017626� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/vlan/cisco.rb������������������������������������������������������0000664�0052762�0001160�00000001040�12650174557�021247� 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-3.8.5/lib/puppet/provider/yumrepo/�����������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020366� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/yumrepo/inifile.rb�������������������������������������������������0000664�0052762�0001160�00000021420�12650174557�022332� 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.keys.each 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 dirs = reposdir.split(",").map(&:strip) if reposdir # We can't use the below due to Ruby 1.8.7 # dirs.select! { |dir| Puppet::FileSystem.exist?(dir) } dirs.delete_if { |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 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 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 inifile = self.virtual_inifile inifile.store target_mode = 0644 inifile.each_file do |file| current_mode = Puppet::FileSystem.stat(file).mode & 0777 unless current_mode == target_mode Puppet.info "changing mode of #{file} from %03o to %03o" % [current_mode, target_mode] Puppet::FileSystem.chmod(target_mode, file) end end 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 new_section = current_section # 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 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-3.8.5/lib/puppet/provider/zfs/���������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017470� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/zfs/zfs.rb���������������������������������������������������������0000664�0052762�0001160�00000005000�12650174557�020613� 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 != "" properties << "-o" << "#{property}=#{value}" end end properties end def create zfs *([:create] + add_properties + [@resource[:name]]) end def destroy zfs(:destroy, @resource[:name]) end def exists? if zfs(:list).split("\n").detect { |line| line.split("\s")[0] == @resource[:name] } true else false end end PARAMETER_UNSET_OR_NOT_AVAILABLE = '-' # http://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 # http://webcache.googleusercontent.com/search?q=cache:-p74K0DVsdwJ:developers.slashdot.org/story/11/11/09/2343258/solaris-11-released+&cd=13 [:aclmode, :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 avaliable. 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, :zoned].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 end puppet-3.8.5/lib/puppet/provider/zone/��������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017641� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/zone/solaris.rb����������������������������������������������������0000664�0052762�0001160�00000022525�12650174557�021651� 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(dummy_argument=:work_arround_for_ruby_GC_bug) 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 File.open(sysidcfg, "w", 0600) 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-3.8.5/lib/puppet/provider/zpool/�������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�020031� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/provider/zpool/zpool.rb�����������������������������������������������������0000664�0052762�0001160�00000006046�12650174557�021530� 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 # http://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-3.8.5/lib/puppet/rails.rb��������������������������������������������������������������������0000664�0052762�0001160�00000011113�12650174557�016471� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Load the appropriate libraries, or set a class indicating they aren't available require 'facter' require 'puppet' require 'logger' module Puppet::Rails TIME_DEBUG = true def self.connect # This global init does not work for testing, because we remove # the state dir on every test. return if ActiveRecord::Base.connected? Puppet.settings.use(:main, :rails, :master) ActiveRecord::Base.logger = Logger.new(Puppet[:railslog]) begin loglevel = Logger.const_get(Puppet[:rails_loglevel].upcase) ActiveRecord::Base.logger.level = loglevel rescue => detail Puppet.warning "'#{Puppet[:rails_loglevel]}' is not a valid Rails log level; using debug" ActiveRecord::Base.logger.level = Logger::DEBUG end # As of ActiveRecord 2.2 allow_concurrency has been deprecated and no longer has any effect. ActiveRecord::Base.allow_concurrency = true if Puppet::Util.activerecord_version < 2.2 ActiveRecord::Base.verify_active_connections! begin args = database_arguments Puppet.info "Connecting to #{args[:adapter]} database: #{args[:database]}" ActiveRecord::Base.establish_connection(args) rescue => detail message = "Could not connect to database: #{detail}" Puppet.log_exception(detail, message) raise Puppet::Error, message, detail.backtrace end end # The arguments for initializing the database connection. def self.database_arguments adapter = Puppet[:dbadapter] args = {:adapter => adapter, :log_level => Puppet[:rails_loglevel]} case adapter when "sqlite3" args[:database] = Puppet[:dblocation] when "mysql", "mysql2", "postgresql" args[:host] = Puppet[:dbserver] unless Puppet[:dbserver].to_s.empty? args[:port] = Puppet[:dbport] unless Puppet[:dbport].to_s.empty? args[:username] = Puppet[:dbuser] unless Puppet[:dbuser].to_s.empty? args[:password] = Puppet[:dbpassword] unless Puppet[:dbpassword].to_s.empty? args[:pool] = Puppet[:dbconnections].to_i unless Puppet[:dbconnections].to_i <= 0 args[:database] = Puppet[:dbname] args[:reconnect]= true socket = Puppet[:dbsocket] args[:socket] = socket unless socket.to_s.empty? when "oracle_enhanced" args[:database] = Puppet[:dbname] unless Puppet[:dbname].to_s.empty? args[:username] = Puppet[:dbuser] unless Puppet[:dbuser].to_s.empty? args[:password] = Puppet[:dbpassword] unless Puppet[:dbpassword].to_s.empty? args[:pool] = Puppet[:dbconnections].to_i unless Puppet[:dbconnections].to_i <= 0 else raise ArgumentError, "Invalid db adapter #{adapter}" end args end # Set up our database connection. It'd be nice to have a "use" system # that could make callbacks. def self.init raise Puppet::DevError, "No activerecord, cannot init Puppet::Rails" unless Puppet.features.rails? connect unless ActiveRecord::Base.connection.tables.include?("resources") require 'puppet/rails/database/schema' Puppet::Rails::Schema.init end migrate if Puppet[:dbmigrate] end # Migrate to the latest db schema. def self.migrate dbdir = nil $LOAD_PATH.each { |d| tmp = File.join(d, "puppet/rails/database") if FileTest.directory?(tmp) dbdir = tmp break end } raise Puppet::Error, "Could not find Puppet::Rails database dir" unless dbdir raise Puppet::Error, "Database has problems, can't migrate." unless ActiveRecord::Base.connection.tables.include?("resources") Puppet.notice "Migrating" begin ActiveRecord::Migrator.migrate(dbdir) rescue => detail message = "Could not migrate database: #{detail}" Puppet.log_exception(detail, message) raise Puppet::Error, "Could not migrate database: #{detail}", detail.backtrace end end # Tear down the database. Mostly only used during testing. def self.teardown raise Puppet::DevError, "No activerecord, cannot init Puppet::Rails" unless Puppet.features.rails? begin Puppet.settings.use(:master, :rails) begin ActiveRecord::Base.establish_connection(database_arguments) rescue => detail Puppet.log_exception(detail) raise Puppet::Error, "Could not connect to database: #{detail}", detail.backtrace end ActiveRecord::Base.connection.tables.each do |t| ActiveRecord::Base.connection.drop_table t end ensure # allow temp files to get cleaned up ActiveRecord::Base.logger.close if ActiveRecord::Base.logger end end end require 'puppet/rails/host' if Puppet.features.rails? �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/rails/����������������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�016146� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/rails/benchmark.rb����������������������������������������������������������0000664�0052762�0001160�00000003210�12650174557�020422� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'benchmark' require 'yaml' module Puppet::Rails::Benchmark $benchmarks = {:accumulated => {}} def time_debug? Puppet::Rails::TIME_DEBUG end def railsmark(message) result = nil seconds = Benchmark.realtime { result = yield } Puppet.debug(message + " in %0.2f seconds" % seconds) $benchmarks[message] = seconds if time_debug? result end def debug_benchmark(message) return yield unless Puppet::Rails::TIME_DEBUG railsmark(message) { yield } end # Collect partial benchmarks to be logged when they're # all done. # These are always low-level debugging so we only # print them if time_debug is enabled. def accumulate_benchmark(message, label) return yield unless time_debug? $benchmarks[:accumulated][message] ||= Hash.new(0) $benchmarks[:accumulated][message][label] += Benchmark.realtime { yield } end # Log the accumulated marks. def log_accumulated_marks(message) return unless time_debug? return if $benchmarks[:accumulated].empty? or $benchmarks[:accumulated][message].nil? or $benchmarks[:accumulated][message].empty? $benchmarks[:accumulated][message].each do |label, value| Puppet.debug(message + ("(#{label})") + (" in %0.2f seconds" % value)) end end def write_benchmarks return unless time_debug? branch = %x{git branch}.split("\n").find { |l| l =~ /^\*/ }.sub("* ", '') file = "/tmp/time_debugging.yaml" if Puppet::FileSystem.exist?(file) data = YAML.load_file(file) else data = {} end data[branch] = $benchmarks Puppet::Util.replace_file(file, 0644) { |f| f.print YAML.dump(data) } end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/rails/database/�������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�017712� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/rails/database/001_add_created_at_to_all_tables.rb��������������������������0000664�0052762�0001160�00000000761�12650174557�026413� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class AddCreatedAtToAllTables < ActiveRecord::Migration def self.up ActiveRecord::Base.connection.tables.each do |t| add_column t.to_s, :created_at, :datetime unless ActiveRecord::Base.connection.columns(t).collect {|c| c.name}.include?("created_at") end end def self.down ActiveRecord::Base.connection.tables.each do |t| remove_column t.to_s, :created_at unless ActiveRecord::Base.connection.columns(t).collect {|c| c.name}.include?("created_at") end end end ���������������puppet-3.8.5/lib/puppet/rails/database/002_remove_duplicated_index_on_all_tables.rb�����������������0000664�0052762�0001160�00000000741�12650174557�030363� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class RemoveDuplicatedIndexOnAllTables < ActiveRecord::Migration def self.up ActiveRecord::Base.connection.tables.each do |t| remove_index t.to_s, :id if ActiveRecord::Base.connection.indexes(t).collect {|c| c.columns}.include?("id") end end def self.down ActiveRecord::Base.connection.tables.each do |t| add_index t.to_s, :id, :integer => true unless ActiveRecord::Base.connection.indexes(t).collect {|c| c.columns}.include?("id") end end end �������������������������������puppet-3.8.5/lib/puppet/rails/database/003_add_environment_to_host.rb�������������������������������0000664�0052762�0001160�00000000570�12650174557�025537� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class AddEnvironmentToHost < ActiveRecord::Migration def self.up add_column :hosts, :environment, :string unless ActiveRecord::Base.connection.columns(:hosts).collect {|c| c.name}.include?("environment") end def self.down remove_column :hosts, :environment if ActiveRecord::Base.connection.columns(:hosts).collect {|c| c.name}.include?("environment") end end ����������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/rails/database/004_add_inventory_service_tables.rb��������������������������0000664�0052762�0001160�00000002237�12650174557�026546� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class AddInventoryServiceTables < ActiveRecord::Migration def self.up unless ActiveRecord::Base.connection.tables.include?("inventory_nodes") create_table :inventory_nodes do |t| t.column :name, :string, :null => false t.column :timestamp, :datetime, :null => false t.column :updated_at, :datetime t.column :created_at, :datetime end add_index :inventory_nodes, :name, :unique => true end unless ActiveRecord::Base.connection.tables.include?("inventory_facts") create_table :inventory_facts, :id => false do |t| t.column :node_id, :integer, :null => false t.column :name, :string, :null => false t.column :value, :text, :null => false end add_index :inventory_facts, [:node_id, :name], :unique => true end end def self.down unless ActiveRecord::Base.connection.tables.include?("inventory_nodes") remove_index :inventory_nodes, :name drop_table :inventory_nodes end if ActiveRecord::Base.connection.tables.include?("inventory_facts") remove_index :inventory_facts, [:node_id, :name] drop_table :inventory_facts end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/rails/database/schema.rb����������������������������������������������������0000664�0052762�0001160�00000011542�12650174557�021503� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'stringio' class Puppet::Rails::Schema def self.init oldout = nil text = '' Puppet::Util.benchmark(Puppet, :notice, "Initialized database") do # We want to rewrite stdout, so we don't get migration messages. oldout = $stdout $stdout = StringIO.new(text, 'w') ActiveRecord::Schema.define do create_table :resources do |t| t.column :title, :text, :null => false t.column :restype, :string, :null => false t.column :host_id, :integer t.column :source_file_id, :integer t.column :exported, :boolean t.column :line, :integer t.column :updated_at, :datetime t.column :created_at, :datetime end add_index :resources, :host_id, :integer => true add_index :resources, :source_file_id, :integer => true # Thanks, mysql! MySQL requires a length on indexes in text fields. # So, we provide them for mysql and handle everything else specially. # Oracle doesn't index on CLOB fields, so we skip it if ['mysql','mysql2'].include? Puppet[:dbadapter] execute "CREATE INDEX typentitle ON resources (restype,title(50));" elsif Puppet[:dbadapter] != "oracle_enhanced" add_index :resources, [:title, :restype] end create_table :source_files do |t| t.column :filename, :string t.column :path, :string t.column :updated_at, :datetime t.column :created_at, :datetime end add_index :source_files, :filename create_table :resource_tags do |t| t.column :resource_id, :integer t.column :puppet_tag_id, :integer t.column :updated_at, :datetime t.column :created_at, :datetime end add_index :resource_tags, :resource_id, :integer => true add_index :resource_tags, :puppet_tag_id, :integer => true create_table :puppet_tags do |t| t.column :name, :string t.column :updated_at, :datetime t.column :created_at, :datetime end # Oracle automatically creates a primary key index add_index :puppet_tags, :id, :integer => true if Puppet[:dbadapter] != "oracle_enhanced" create_table :hosts do |t| t.column :name, :string, :null => false t.column :ip, :string t.column :environment, :text t.column :last_compile, :datetime t.column :last_freshcheck, :datetime t.column :last_report, :datetime #Use updated_at to automatically add timestamp on save. t.column :updated_at, :datetime t.column :source_file_id, :integer t.column :created_at, :datetime end add_index :hosts, :source_file_id, :integer => true add_index :hosts, :name create_table :fact_names do |t| t.column :name, :string, :null => false t.column :updated_at, :datetime t.column :created_at, :datetime end add_index :fact_names, :name create_table :fact_values do |t| t.column :value, :text, :null => false t.column :fact_name_id, :integer, :null => false t.column :host_id, :integer, :null => false t.column :updated_at, :datetime t.column :created_at, :datetime end add_index :fact_values, :fact_name_id, :integer => true add_index :fact_values, :host_id, :integer => true create_table :param_values do |t| t.column :value, :text, :null => false t.column :param_name_id, :integer, :null => false t.column :line, :integer t.column :resource_id, :integer t.column :updated_at, :datetime t.column :created_at, :datetime end add_index :param_values, :param_name_id, :integer => true add_index :param_values, :resource_id, :integer => true create_table :param_names do |t| t.column :name, :string, :null => false t.column :updated_at, :datetime t.column :created_at, :datetime end add_index :param_names, :name create_table :inventory_nodes do |t| t.column :name, :string, :null => false t.column :timestamp, :datetime, :null => false t.column :updated_at, :datetime t.column :created_at, :datetime end add_index :inventory_nodes, :name, :unique => true create_table :inventory_facts do |t| t.column :node_id, :integer, :null => false t.column :name, :string, :null => false t.column :value, :text, :null => false end add_index :inventory_facts, [:node_id, :name], :unique => true end end rescue Exception => e $stderr.puts e $stderr.puts "The output from running the code was:", text raise e ensure $stdout = oldout if oldout end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/rails/fact_name.rb����������������������������������������������������������0000664�0052762�0001160�00000000271�12650174557�020411� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'active_record' require 'puppet/rails' require 'puppet/rails/fact_value' class Puppet::Rails::FactName < ActiveRecord::Base has_many :fact_values, :dependent => :destroy end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/rails/fact_value.rb���������������������������������������������������������0000664�0052762�0001160�00000000257�12650174557�020611� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'active_record' class Puppet::Rails::FactValue < ActiveRecord::Base belongs_to :fact_name belongs_to :host def to_label "#{self.fact_name.name}" end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/rails/host.rb���������������������������������������������������������������0000664�0052762�0001160�00000017015�12650174557�017455� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node/environment' require 'puppet/rails' require 'puppet/rails/resource' require 'puppet/rails/fact_name' require 'puppet/rails/source_file' require 'puppet/rails/benchmark' require 'puppet/util/rails/collection_merger' class Puppet::Rails::Host < ActiveRecord::Base include Puppet::Rails::Benchmark extend Puppet::Rails::Benchmark include Puppet::Util include Puppet::Util::CollectionMerger has_many :fact_values, :dependent => :destroy, :class_name => "Puppet::Rails::FactValue" has_many :fact_names, :through => :fact_values, :class_name => "Puppet::Rails::FactName" belongs_to :source_file has_many :resources, :dependent => :destroy, :class_name => "Puppet::Rails::Resource" def self.from_puppet(node) host = find_by_name(node.name) || new(:name => node.name) {"ipaddress" => "ip", "environment" => "environment"}.each do |myparam, itsparam| if value = node.send(myparam) host.send(itsparam + "=", value) end end host end # Override the setter for environment to force it to be a string, lest it # be YAML encoded. See #4487. def environment=(value) super value.to_s end # returns a hash of fact_names.name => [ fact_values ] for this host. # Note that 'fact_values' is actually a list of the value instances, not # just actual values. def get_facts_hash fact_values = self.fact_values.find(:all, :include => :fact_name) return fact_values.inject({}) do | hash, value | hash[value.fact_name.name] ||= [] hash[value.fact_name.name] << value hash end end # This is *very* similar to the merge_parameters method # of Puppet::Rails::Resource. def merge_facts(facts) db_facts = {} deletions = [] self.fact_values.find(:all, :include => :fact_name).each do |value| deletions << value['id'] and next unless facts.include?(value['name']) # Now store them for later testing. db_facts[value['name']] ||= [] db_facts[value['name']] << value end # Now get rid of any parameters whose value list is different. # This might be extra work in cases where an array has added or lost # a single value, but in the most common case (a single value has changed) # this makes sense. db_facts.each do |name, value_hashes| values = value_hashes.collect { |v| v['value'] } unless values == facts[name] value_hashes.each { |v| deletions << v['id'] } end end # Perform our deletions. Puppet::Rails::FactValue.delete(deletions) unless deletions.empty? # Lastly, add any new parameters. facts.each do |name, value| next if db_facts.include?(name) values = value.is_a?(Array) ? value : [value] values.each do |v| fact_values.build(:value => v, :fact_name => Puppet::Rails::FactName.find_or_create_by_name(name)) end end end # Set our resources. def merge_resources(list) # keep only exported resources in thin_storeconfig mode list = list.select { |r| r.exported? } if Puppet.settings[:thin_storeconfigs] resources_by_id = nil debug_benchmark("Searched for resources") { resources_by_id = find_resources } debug_benchmark("Searched for resource params and tags") { find_resources_parameters_tags(resources_by_id) } if id debug_benchmark("Performed resource comparison") { compare_to_catalog(resources_by_id, list) } end def find_resources condition = { :exported => true } if Puppet.settings[:thin_storeconfigs] resources.find(:all, :include => :source_file, :conditions => condition || {}).inject({}) do | hash, resource | hash[resource.id] = resource hash end end def find_resources_parameters_tags(resources) find_resources_parameters(resources) find_resources_tags(resources) end def compare_to_catalog(existing, list) compiled = list.inject({}) do |hash, resource| hash[resource.ref] = resource hash end resources = nil debug_benchmark("Resource removal") { resources = remove_unneeded_resources(compiled, existing) } # Now for all resources in the catalog but not in the db, we're pretty easy. additions = nil debug_benchmark("Resource merger") { additions = perform_resource_merger(compiled, resources) } debug_benchmark("Resource addition") { additions.each do |resource| build_rails_resource_from_parser_resource(resource) end log_accumulated_marks "Added resources" } end def add_new_resources(additions) additions.each do |resource| Puppet::Rails::Resource.from_parser_resource(self, resource) end end # Turn a parser resource into a Rails resource. def build_rails_resource_from_parser_resource(resource) db_resource = nil accumulate_benchmark("Added resources", :initialization) { args = Puppet::Rails::Resource.rails_resource_initial_args(resource) db_resource = self.resources.build(args) # Our file= method does the name to id conversion. db_resource.file = resource.file } accumulate_benchmark("Added resources", :parameters) { resource.each do |param, value| Puppet::Rails::ParamValue.from_parser_param(param, value).each do |value_hash| db_resource.param_values.build(value_hash) end end } accumulate_benchmark("Added resources", :tags) { resource.tags.each { |tag| db_resource.add_resource_tag(tag) } } db_resource.save db_resource end def perform_resource_merger(compiled, resources) return compiled.values if resources.empty? # Now for all resources in the catalog but not in the db, we're pretty easy. additions = [] compiled.each do |ref, resource| if db_resource = resources[ref] db_resource.merge_parser_resource(resource) else additions << resource end end log_accumulated_marks "Resource merger" additions end def remove_unneeded_resources(compiled, existing) deletions = [] resources = {} existing.each do |id, resource| # it seems that it can happen (see bug #2010) some resources are duplicated in the # database (ie logically corrupted database), in which case we remove the extraneous # entries. if resources.include?(resource.ref) deletions << id next end # If the resource is in the db but not in the catalog, mark it # for removal. unless compiled.include?(resource.ref) deletions << id next end resources[resource.ref] = resource end # We need to use 'destroy' here, not 'delete', so that all # dependent objects get removed, too. Puppet::Rails::Resource.destroy(deletions) unless deletions.empty? resources end def find_resources_parameters(resources) params = Puppet::Rails::ParamValue.find_all_params_from_host(self) # assign each loaded parameters/tags to the resource it belongs to params.each do |param| resources[param['resource_id']].add_param_to_list(param) if resources.include?(param['resource_id']) end end def find_resources_tags(resources) tags = Puppet::Rails::ResourceTag.find_all_tags_from_host(self) tags.each do |tag| resources[tag['resource_id']].add_tag_to_list(tag) if resources.include?(tag['resource_id']) end end def to_puppet node = Puppet::Node.new(self.name) {"ip" => "ipaddress", "environment" => "environment"}.each do |myparam, itsparam| if value = send(myparam) node.send(itsparam + "=", value) end end node end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/rails/inventory_fact.rb�����������������������������������������������������0000664�0052762�0001160�00000000247�12650174557�021531� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/rails/inventory_node' class Puppet::Rails::InventoryFact < ::ActiveRecord::Base belongs_to :node, :class_name => "Puppet::Rails::InventoryNode" end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/rails/inventory_node.rb�����������������������������������������������������0000664�0052762�0001160�00000002513�12650174557�021537� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/rails/inventory_fact' class Puppet::Rails::InventoryNode < ::ActiveRecord::Base has_many :facts, :class_name => "Puppet::Rails::InventoryFact", :foreign_key => :node_id, :dependent => :delete_all if Puppet::Util.activerecord_version < 3.0 # For backward compatibility, use the old named_scope with pre 3.0 activerecord named_scope :has_fact_with_value, lambda { |name,value| { :conditions => ["inventory_facts.name = ? AND inventory_facts.value = ?", name, value.to_s], :joins => :facts } } named_scope :has_fact_without_value, lambda { |name,value| { :conditions => ["inventory_facts.name = ? AND inventory_facts.value != ?", name, value.to_s], :joins => :facts } } else # Use scope for activerecord >= 3.0 scope :has_fact_with_value, lambda { |name,value| { :conditions => ["inventory_facts.name = ? AND inventory_facts.value = ?", name, value.to_s], :joins => :facts } } scope :has_fact_without_value, lambda { |name,value| { :conditions => ["inventory_facts.name = ? AND inventory_facts.value != ?", name, value.to_s], :joins => :facts } } end def facts_to_hash facts.inject({}) do |fact_hash,fact| fact_hash.merge(fact.name => fact.value) end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/rails/param_name.rb���������������������������������������������������������0000664�0052762�0001160�00000001375�12650174557�020602� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/rails/collection_merger' require 'puppet/rails/param_value' require 'puppet/util/rails/cache_accumulator' class Puppet::Rails::ParamName < ActiveRecord::Base include Puppet::Util::CollectionMerger has_many :param_values, :dependent => :destroy include Puppet::Util::CacheAccumulator accumulates :name def to_resourceparam(resource, source) hash = {} hash[:name] = self.name.to_sym hash[:source] = source hash[:value] = resource.param_values.find(:all, :conditions => [ "param_name_id = ?", self.id]).collect { |v| v.value } if hash[:value].length == 1 hash[:value] = hash[:value].shift elsif hash[:value].empty? hash[:value] = nil end Puppet::Parser::Resource::Param.new hash end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/rails/param_value.rb��������������������������������������������������������0000664�0052762�0001160�00000004671�12650174557�021000� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/rails/reference_serializer' class Puppet::Rails::ParamValue < ActiveRecord::Base include Puppet::Util::ReferenceSerializer extend Puppet::Util::ReferenceSerializer belongs_to :param_name belongs_to :resource # Store a new parameter in a Rails db. def self.from_parser_param(param, values) values = munge_parser_values(values) param_name = Puppet::Rails::ParamName.find_or_create_by_name(param.to_s) return values.collect do |v| {:value => v, :param_name => param_name} end end # Make sure an array (or possibly not an array) of values is correctly # set up for Rails. The main thing is that Resource::Reference objects # should stay objects, so they just get serialized. def self.munge_parser_values(value) values = value.is_a?(Array) ? value : [value] values.map do |v| if v.is_a?(Puppet::Resource) v else v.to_s end end end def value unserialize_value(self[:value]) end # I could not find a cleaner way to handle making sure that resource references # were consistently serialized and deserialized. def value=(val) self[:value] = serialize_value(val) end def to_label "#{self.param_name.name}" end # returns an array of hash containing all the parameters of a given resource def self.find_all_params_from_resource(db_resource) params = db_resource.connection.select_all("SELECT v.id, v.value, v.line, v.resource_id, v.param_name_id, n.name FROM param_values v INNER JOIN param_names n ON v.param_name_id=n.id WHERE v.resource_id=#{db_resource.id}") params.each do |val| val['value'] = unserialize_value(val['value']) val['line'] = val['line'] ? Integer(val['line']) : nil val['resource_id'] = Integer(val['resource_id']) end params end # returns an array of hash containing all the parameters of a given host def self.find_all_params_from_host(db_host) params = db_host.connection.select_all("SELECT v.id, v.value, v.line, v.resource_id, v.param_name_id, n.name FROM param_values v INNER JOIN resources r ON v.resource_id=r.id INNER JOIN param_names n ON v.param_name_id=n.id WHERE r.host_id=#{db_host.id}") params.each do |val| val['value'] = unserialize_value(val['value']) val['line'] = val['line'] ? Integer(val['line']) : nil val['resource_id'] = Integer(val['resource_id']) end params end def to_s "#{self.name} => #{self.value}" end end �����������������������������������������������������������������������puppet-3.8.5/lib/puppet/rails/puppet_tag.rb���������������������������������������������������������0000664�0052762�0001160�00000000455�12650174557�020650� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/rails/resource_tag' require 'puppet/util/rails/cache_accumulator' class Puppet::Rails::PuppetTag < ActiveRecord::Base has_many :resource_tags, :dependent => :destroy has_many :resources, :through => :resource_tags include Puppet::Util::CacheAccumulator accumulates :name end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/rails/resource.rb�����������������������������������������������������������0000664�0052762�0001160�00000015216�12650174557�020330� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/rails/param_name' require 'puppet/rails/param_value' require 'puppet/rails/puppet_tag' require 'puppet/rails/benchmark' require 'puppet/util/rails/collection_merger' class Puppet::Rails::Resource < ActiveRecord::Base include Puppet::Util::CollectionMerger include Puppet::Util::ReferenceSerializer include Puppet::Rails::Benchmark has_many :param_values, :dependent => :destroy, :class_name => "Puppet::Rails::ParamValue" has_many :param_names, :through => :param_values, :class_name => "Puppet::Rails::ParamName" has_many :resource_tags, :dependent => :destroy, :class_name => "Puppet::Rails::ResourceTag" has_many :puppet_tags, :through => :resource_tags, :class_name => "Puppet::Rails::PuppetTag" belongs_to :source_file belongs_to :host @tags = {} def self.tags @tags end # Determine the basic details on the resource. def self.rails_resource_initial_args(resource) result = [:type, :title, :line].inject({}) do |hash, param| # 'type' isn't a valid column name, so we have to use another name. to = (param == :type) ? :restype : param if value = resource.send(param) hash[to] = value end hash end # We always want a value here, regardless of what the resource has, # so we break it out separately. result[:exported] = resource.exported || false result end def add_resource_tag(tag) pt = Puppet::Rails::PuppetTag.accumulate_by_name(tag) resource_tags.build(:puppet_tag => pt) end def file (f = self.source_file) ? f.filename : nil end def file=(file) self.source_file = Puppet::Rails::SourceFile.find_or_create_by_filename(file) end def title unserialize_value(self[:title]) end def params_list @params_list ||= [] end def params_list=(params) @params_list = params end def add_param_to_list(param) params_list << param end def tags_list @tags_list ||= [] end def tags_list=(tags) @tags_list = tags end def add_tag_to_list(tag) tags_list << tag end def [](param) if param == 'id' super else super || parameter(param) end end # Make sure this resource is equivalent to the provided Parser resource. def merge_parser_resource(resource) accumulate_benchmark("Individual resource merger", :attributes) { merge_attributes(resource) } accumulate_benchmark("Individual resource merger", :parameters) { merge_parameters(resource) } accumulate_benchmark("Individual resource merger", :tags) { merge_tags(resource) } save end def merge_attributes(resource) args = self.class.rails_resource_initial_args(resource) args.each do |param, value| self[param] = value unless resource[param] == value end # Handle file specially self.file = resource.file if (resource.file and (!resource.file or self.file != resource.file)) end def merge_parameters(resource) catalog_params = {} resource.each do |param, value| catalog_params[param.to_s] = value end db_params = {} deletions = [] params_list.each do |value| # First remove any parameters our catalog resource doesn't have at all. deletions << value['id'] and next unless catalog_params.include?(value['name']) # Now store them for later testing. db_params[value['name']] ||= [] db_params[value['name']] << value end # Now get rid of any parameters whose value list is different. # This might be extra work in cases where an array has added or lost # a single value, but in the most common case (a single value has changed) # this makes sense. db_params.each do |name, value_hashes| values = value_hashes.collect { |v| v['value'] } value_hashes.each { |v| deletions << v['id'] } unless value_compare(catalog_params[name], values) end # Perform our deletions. Puppet::Rails::ParamValue.delete(deletions) unless deletions.empty? # Lastly, add any new parameters. catalog_params.each do |name, value| next if db_params.include?(name) && ! db_params[name].find{ |val| deletions.include?( val["id"] ) } values = value.is_a?(Array) ? value : [value] values.each do |v| param_values.build(:value => serialize_value(v), :line => resource.line, :param_name => Puppet::Rails::ParamName.accumulate_by_name(name)) end end end # Make sure the tag list is correct. def merge_tags(resource) in_db = [] deletions = [] resource_tags = resource.tags tags_list.each do |tag| deletions << tag['id'] and next unless resource_tags.include?(tag['name']) in_db << tag['name'] end Puppet::Rails::ResourceTag.delete(deletions) unless deletions.empty? (resource_tags - in_db).each do |tag| add_resource_tag(tag) end end def value_compare(v,db_value) v = [v] unless v.is_a?(Array) v == db_value end def name ref end def parameter(param) if pn = param_names.find_by_name(param) return (pv = param_values.find(:first, :conditions => [ 'param_name_id = ?', pn])) ? pv.value : nil end end def ref(dummy_argument=:work_arround_for_ruby_GC_bug) "#{self[:restype].split("::").collect { |s| s.capitalize }.join("::")}[#{self.title}]" end # Returns a hash of parameter names and values, no ActiveRecord instances. def to_hash Puppet::Rails::ParamValue.find_all_params_from_resource(self).inject({}) do |hash, value| hash[value['name']] ||= [] hash[value['name']] << value.value hash end end # Convert our object to a resource. Do not retain whether the object # is exported, though, since that would cause it to get stripped # from the configuration. def to_resource(scope) hash = self.attributes hash["type"] = hash["restype"] hash.delete("restype") # FIXME At some point, we're going to want to retain this information # for logging and auditing. hash.delete("host_id") hash.delete("updated_at") hash.delete("source_file_id") hash.delete("created_at") hash.delete("id") hash.each do |p, v| hash.delete(p) if v.nil? end hash[:scope] = scope hash[:source] = scope.source hash[:parameters] = [] names = [] self.param_names.each do |pname| # We can get the same name multiple times because of how the # db layout works. next if names.include?(pname.name) names << pname.name hash[:parameters] << pname.to_resourceparam(self, scope.source) end obj = Puppet::Parser::Resource.new(hash.delete("type"), hash.delete("title"), hash) # Store the ID, so we can check if we're re-collecting the same resource. obj.collector_id = self.id obj end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/rails/resource_tag.rb�������������������������������������������������������0000664�0052762�0001160�00000001731�12650174557�021160� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Rails::ResourceTag < ActiveRecord::Base belongs_to :puppet_tag belongs_to :resource def to_label "#{self.puppet_tag.name}" end # returns an array of hash containing tags of resource def self.find_all_tags_from_resource(db_resource) tags = db_resource.connection.select_all("SELECT t.id, t.resource_id, p.name FROM resource_tags t INNER JOIN puppet_tags p ON t.puppet_tag_id=p.id WHERE t.resource_id=#{db_resource.id}") tags.each do |val| val['resource_id'] = Integer(val['resource_id']) end tags end # returns an array of hash containing tags of a host def self.find_all_tags_from_host(db_host) tags = db_host.connection.select_all("SELECT t.id, t.resource_id, p.name FROM resource_tags t INNER JOIN resources r ON t.resource_id=r.id INNER JOIN puppet_tags p ON t.puppet_tag_id=p.id WHERE r.host_id=#{db_host.id}") tags.each do |val| val['resource_id'] = Integer(val['resource_id']) end tags end end ���������������������������������������puppet-3.8.5/lib/puppet/rails/source_file.rb��������������������������������������������������������0000664�0052762�0001160�00000000212�12650174557�020766� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Rails::SourceFile < ActiveRecord::Base has_one :host has_one :resource def to_label "#{self.filename}" end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/reference/������������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�016772� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/reference/configuration.rb��������������������������������������������������0000664�0052762�0001160�00000004567�12650174557�022203� 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 = "/var/lib/puppet" elsif name.to_s == "confdir" val = "/etc/puppet" 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. * When using boolean settings on the command line, use `--setting` and `--no-setting` instead of `--setting (true|false)`. * 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). * 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]: http://docs.puppetlabs.com/guides/configuring.html * * * EOT �����������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/reference/function.rb�������������������������������������������������������0000664�0052762�0001160�00000001302�12650174557�021141� 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-3.8.5/lib/puppet/reference/indirection.rb����������������������������������������������������0000664�0052762�0001160�00000007004�12650174557�021630� 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.inspect}, terminus #{terminus_name.inspect}: could not locate terminus." 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 van 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 `/{environment}/{indirection}/{key}`, 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-3.8.5/lib/puppet/reference/metaparameter.rb��������������������������������������������������0000664�0052762�0001160�00000002225�12650174557�022150� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Util::Reference.newreference :metaparameter, :doc => "All Puppet metaparameters and all their details" do types = {} Puppet::Type.loadall Puppet::Type.eachtype { |type| next if type.name == :puppet next if type.name == :component types[type.name] = type } 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}") exit(1) end str end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/reference/providers.rb������������������������������������������������������0000664�0052762�0001160�00000007417�12650174557�021346� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This doesn't get stored in trac, since it changes every time. 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.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 #{values.join(", ")}\n" when :variable values.each do |name, facts| if Puppet.settings.valid?(name) details << " - Setting #{name} (currently #{Puppet.settings.value(name).inspect}) not in list #{facts.join(", ")}\n" else details << " - Fact #{name} (currently #{Facter.value(name).inspect}) not in list #{facts.join(", ")}\n" end end when :true details << " - Got #{values} true tests that should have been false\n" when :false details << " - Got #{values} false tests that should have been true\n" when :feature details << " - Missing features #{values.collect { |f| f.to_s }.join(",")}\n" 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}](#{"http://docs.puppetlabs.com/references/stable/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-3.8.5/lib/puppet/reference/report.rb���������������������������������������������������������0000664�0052762�0001160�00000001676�12650174557�020645� 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 clients can report back to the server after each transaction. This transaction report is sent as a YAML dump of the `Puppet::Transaction::Report` class and includes every log message that was generated during the transaction along with as many metrics as Puppet knows how to collect. See [Reports and Reporting](http://docs.puppetlabs.com/guides/reporting.html) for more information on how to use reports. Currently, clients default to not sending in reports; you can enable reporting by setting the `report` parameter to true. To use a report, set the `reports` parameter on the server; multiple reports must be comma-separated. You can also specify `none` to disable reports entirely. Puppet provides multiple report handlers that will process client reports: " ������������������������������������������������������������������puppet-3.8.5/lib/puppet/reference/type.rb�����������������������������������������������������������0000664�0052762�0001160�00000006504�12650174557�020306� 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 == :puppet 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 => 644 } `/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 qualifed 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}" unless property doc = nil unless doc = property.doc $stderr.puts "No docs for #{type}[#{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 { |name,param| #docs[name] = indent(scrub(type.paramdoc(name)), $tab) docs[name] = scrub(type.paramdoc(name)) } additional_key_attributes = type.key_attributes - [:name] docs.sort { |a, b| a[0].to_s <=> b[0].to_s }.each { |name, doc| if additional_key_attributes.include?(name) doc = "(**Namevar:** If omitted, this parameter's value defaults to the resource's title.)\n\n" + doc end str << markdown_definitionlist(name, doc) } str << "\n" } str end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/relationship.rb�������������������������������������������������������������0000664�0052762�0001160�00000004404�12650174557�020065� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# subscriptions are permanent associations determining how different # objects react to an event require 'puppet/util/pson' # 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 extend Puppet::Util::Pson 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 self.from_pson(pson) Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.") self.from_data_hash(pson) end def event=(event) 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 } ["event", "callback"].each do |attr| next unless value = send(attr) data[attr] = value end data end # This doesn't include document type as it is part of a catalog def to_pson_data_hash to_data_hash end def to_pson(*args) to_data_hash.to_pson(*args) end def to_s ref end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/reports.rb������������������������������������������������������������������0000664�0052762�0001160�00000005517�12650174557�017070� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require '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), :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-3.8.5/lib/puppet/reports/��������������������������������������������������������������������0000775�0052762�0001160�00000000000�12650174565�016532� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/reports/http.rb�������������������������������������������������������������0000664�0052762�0001160�00000002024�12650174557�020035� 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. 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" } options = {} 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 #{Puppet[:reporturl].to_s} [#{response.code}] #{response.msg}" end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-3.8.5/lib/puppet/reports/log.rb��������������������������������������������������������������0000664�0052762�0001160�00000000506�12650174557�017642� 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-3.8.5/lib/puppet/reports/rrdgraph.rb���������������������������������������������������������0000664�0052762�0001160�00000010251�12650174557�020670� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Reports.register_report(:rrdgraph) do desc "Graph all available data about hosts using the RRD library. You must have the Ruby RRDtool library installed to use this report, which you can get from [the RubyRRDTool RubyForge page](http://rubyforge.org/projects/rubyrrdtool/). This package may also be available as `librrd-ruby`, `ruby-rrd`, or `rrdtool-ruby` in your distribution's package management system. The library and/or package will both require the binary `rrdtool` package from your distribution to be installed. This report will create, manage, and graph RRD database files for each of the metrics generated during transactions, and it will create a few simple html files to display the reporting host's graphs. At this point, it will not create a common index file to display links to all hosts. All RRD files and graphs get created in the `rrddir` directory. If you want to serve these publicly, you should be able to just alias that directory in a web server. If you really know what you're doing, you can tune the `rrdinterval`, which defaults to the `runinterval`." def hostdir @hostdir ||= File.join(Puppet[:rrddir], self.host) end def htmlfile(type, graphs, field) file = File.join(hostdir, "#{type}.html") File.open(file, "w") do |of| of.puts "<html><head><title>#{type.capitalize} graphs for #{host}" graphs.each do |graph| if field == :first name = graph.sub(/-\w+.png/, '').capitalize else name = graph.sub(/\w+-/, '').sub(".png", '').capitalize end of.puts "
" end of.puts "" end file end def mkhtml images = Dir.entries(hostdir).find_all { |d| d =~ /\.png/ } periodorder = %w{daily weekly monthly yearly} periods = {} types = {} images.each do |n| type, period = n.sub(".png", '').split("-") periods[period] ||= [] types[type] ||= [] periods[period] << n types[type] << n end files = [] # Make the period html files periodorder.each do |period| unless ary = periods[period] raise Puppet::Error, "Could not find graphs for #{period}" end files << htmlfile(period, ary, :first) end # make the type html files types.sort { |a,b| a[0] <=> b[0] }.each do |type, ary| newary = [] periodorder.each do |period| if graph = ary.find { |g| g.include?("-#{period}.png") } newary << graph else raise "Could not find #{type}-#{period} graph" end end files << htmlfile(type, newary, :second) end File.open(File.join(hostdir, "index.html"), "w") do |of| of.puts "Report graphs for #{host}" files.each do |file| of.puts "#{File.basename(file).sub(".html",'').capitalize}
" end of.puts "" end end def process(time = nil) time ||= Time.now.to_i unless File.directory?(hostdir) and FileTest.writable?(hostdir) # Some hackishness to create the dir with all of the right modes and ownership config = Puppet::Settings.new config.define_settings(:reports, :hostdir => {:type => :directory, :default => hostdir, :owner => 'service', :mode => 0755, :group => 'service', :desc => "eh"}) # This creates the dir. config.use(:reports) end self.metrics.each do |name, metric| metric.basedir = hostdir if name == "time" timeclean(metric) end metric.store(time) metric.graph end mkhtml unless Puppet::FileSystem.exist?(File.join(hostdir, "index.html")) end # Unfortunately, RRD does not deal well with changing lists of values, # so we have to pick a list of values and stick with it. In this case, # that means we record the total time, the config time, and that's about # it. We should probably send each type's time as a separate metric. def timeclean(metric) metric.values = metric.values.find_all { |name, label, value| ['total', 'config_retrieval'].include?(name.to_s) } end end puppet-3.8.5/lib/puppet/reports/store.rb0000664005276200011600000000353612650174557020223 0ustar jenkinsjenkinsrequire '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.inspect}" end end module_function :validate_host end puppet-3.8.5/lib/puppet/reports/tagmail.rb0000664005276200011600000001343312650174557020502 0ustar jenkinsjenkinsrequire 'puppet' require 'pp' require 'net/smtp' require 'time' Puppet::Reports.register_report(:tagmail) do desc "This report sends specific log messages to specific email addresses based on the tags in the log messages. See the [documentation on tags](http://docs.puppetlabs.com/puppet/latest/reference/lang_tags.html) for more information. To use this report, you must create a `tagmail.conf` file in the location specified by the `tagmap` setting. This is a simple file that maps tags to email addresses: Any log messages in the report that match the specified tags will be sent to the specified email addresses. Lines in the `tagmail.conf` file consist of a comma-separated list of tags, a colon, and a comma-separated list of email addresses. Tags can be !negated with a leading exclamation mark, which will subtract any messages with that tag from the set of events handled by that line. Puppet's log levels (`debug`, `info`, `notice`, `warning`, `err`, `alert`, `emerg`, `crit`, and `verbose`) can also be used as tags, and there is an `all` tag that will always match all log messages. An example `tagmail.conf`: all: me@domain.com webserver, !mailserver: httpadmins@domain.com This will send all messages to `me@domain.com`, and all messages from webservers that are not also from mailservers to `httpadmins@domain.com`. If you are using anti-spam controls such as grey-listing on your mail server, you should whitelist the sending email address (controlled by `reportfrom` configuration option) to ensure your email is not discarded as spam. " # Find all matching messages. def match(taglists) matching_logs = [] taglists.each do |emails, pos, neg| # First find all of the messages matched by our positive tags messages = nil if pos.include?("all") messages = self.logs else # Find all of the messages that are tagged with any of our # tags. messages = self.logs.find_all do |log| pos.detect { |tag| log.tagged?(tag) } end end # Now go through and remove any messages that match our negative tags messages = messages.reject do |log| true if neg.detect do |tag| log.tagged?(tag) end end if messages.empty? Puppet.info "No messages to report to #{emails.join(",")}" next else matching_logs << [emails, messages.collect { |m| m.to_report }.join("\n")] end end matching_logs end # Load the config file def parse(text) taglists = [] text.split("\n").each do |line| taglist = emails = nil case line.chomp when /^\s*#/; next when /^\s*$/; next when /^\s*(.+)\s*:\s*(.+)\s*$/ taglist = $1 emails = $2.sub(/#.*$/,'') else raise ArgumentError, "Invalid tagmail config file" end pos = [] neg = [] taglist.sub(/\s+$/,'').split(/\s*,\s*/).each do |tag| unless tag =~ /^!?[-\w\.]+$/ raise ArgumentError, "Invalid tag #{tag.inspect}" end case tag when /^\w+/; pos << tag when /^!\w+/; neg << tag.sub("!", '') else raise Puppet::Error, "Invalid tag '#{tag}'" end end # Now split the emails emails = emails.sub(/\s+$/,'').split(/\s*,\s*/) taglists << [emails, pos, neg] end taglists end # Process the report. This just calls the other associated messages. def process unless Puppet::FileSystem.exist?(Puppet[:tagmap]) Puppet.notice "Cannot send tagmail report; no tagmap file #{Puppet[:tagmap]}" return end metrics = raw_summary['resources'] || {} rescue {} if metrics['out_of_sync'] == 0 && metrics['changed'] == 0 Puppet.notice "Not sending tagmail report; no changes" return end taglists = parse(File.read(Puppet[:tagmap])) # Now find any appropriately tagged messages. reports = match(taglists) send(reports) unless reports.empty? end # Send the email reports. def send(reports) pid = Puppet::Util.safe_posix_fork do if Puppet[:smtpserver] != "none" begin Net::SMTP.start(Puppet[:smtpserver], Puppet[:smtpport], Puppet[:smtphelo]) do |smtp| reports.each do |emails, messages| smtp.open_message_stream(Puppet[:reportfrom], *emails) do |p| p.puts "From: #{Puppet[:reportfrom]}" p.puts "Subject: Puppet Report for #{self.host}" p.puts "To: " + emails.join(", ") p.puts "Date: #{Time.now.rfc2822}" p.puts p.puts messages end end end rescue => detail message = "Could not send report emails through smtp: #{detail}" Puppet.log_exception(detail, message) raise Puppet::Error, message, detail.backtrace end elsif Puppet[:sendmail] != "" begin reports.each do |emails, messages| # We need to open a separate process for every set of email addresses IO.popen(Puppet[:sendmail] + " " + emails.join(" "), "w") do |p| p.puts "From: #{Puppet[:reportfrom]}" p.puts "Subject: Puppet Report for #{self.host}" p.puts "To: " + emails.join(", ") p.puts p.puts messages end end rescue => detail message = "Could not send report emails via sendmail: #{detail}" Puppet.log_exception(detail, message) raise Puppet::Error, message, detail.backtrace end else raise Puppet::Error, "SMTP server is unset and could not find sendmail" end end # Don't bother waiting for the pid to return. Process.detach(pid) end end puppet-3.8.5/lib/puppet/resource.rb0000664005276200011600000004372512650174557017224 0ustar jenkinsjenkinsrequire 'puppet' require 'puppet/util/tagging' require 'puppet/util/pson' 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 # This stub class is only needed for serialization compatibility with 0.25.x. # Specifically, it exists to provide a compatibility API when using YAML # serialized objects loaded from StoreConfigs. Reference = Puppet::Resource include Puppet::Util::Tagging extend Puppet::Util::Pson include Enumerable attr_accessor :file, :line, :catalog, :exported, :virtual, :validate_parameters, :strict attr_reader :type, :title require 'puppet/indirector' extend Puppet::Indirector indirects :resource, :terminus_class => :ral ATTRIBUTES = [:file, :line, :exported] def self.from_data_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'] resource = new(type, title) if params = data['parameters'] params.each { |param, value| resource[param] = value } end if tags = data['tags'] tags.each { |tag| resource.tag(tag) } end ATTRIBUTES.each do |a| if value = data[a.to_s] resource.send(a.to_s + "=", value) end end resource end def self.from_pson(pson) Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.") self.from_data_hash(pson) end def inspect "#{@type}[#{@title}]#{to_hash.inspect}" end def to_data_hash data = ([:type, :title, :tags] + ATTRIBUTES).inject({}) do |hash, param| next hash unless value = self.send(param) hash[param.to_s] = value hash end data["exported"] ||= false params = self.to_hash.inject({}) do |hash, ary| param, value = ary # Don't duplicate the title as the namevar next hash if param == namevar and value == title hash[param] = Puppet::Resource.value_to_pson_data(value) hash end data["parameters"] = params unless params.empty? data end # This doesn't include document type as it is part of a catalog def to_pson_data_hash to_data_hash end def self.value_to_pson_data(value) if value.is_a? Array value.map{|v| value_to_pson_data(v) } elsif value.is_a? Puppet::Resource value.to_s else value end end def yaml_property_munge(x) case x when Hash x.inject({}) { |h,kv| k,v = kv h[k] = self.class.value_to_pson_data(v) h } else self.class.value_to_pson_data(x) end end YAML_ATTRIBUTES = [:@file, :@line, :@exported, :@type, :@title, :@tags, :@parameters] # Explicitly list the instance variables that should be serialized when # converting to YAML. # # @api private # @return [Array] The intersection of our explicit variable list and # all of the instance variables defined on this class. def to_yaml_properties YAML_ATTRIBUTES & super end def to_pson(*args) to_data_hash.to_pson(*args) 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? builtin_type? end # Is this a builtin resource type? def builtin_type? resource_type.is_a?(Class) 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 == "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 = {}) @parameters = {} environment = attributes[:environment] if type.is_a?(Class) && type < Puppet::Type # Set the resource type to avoid an expensive `known_resource_types` # lookup. self.resource_type = type # From this point on, the constructor behaves the same as if `type` had # been passed as a symbol. type = type.name end # Set things like strictness first. attributes.each do |attr, value| next if attr == :parameters send(attr.to_s + "=", value) end @type, @title = extract_type_and_title(type, title) @type = munge_type_name(@type) if self.class? @title = :main if @title == "" @title = munge_type_name(@title) end if params = attributes[:parameters] extract_parameters(params) end if resource_type && resource_type.respond_to?(:deprecate_params) resource_type.deprecate_params(title, attributes[:parameters]) end tag(self.type) tag(self.title) if valid_tag?(self.title) @reference = self # for serialization compatibility with 0.25.x if strict? and ! resource_type if self.class? raise ArgumentError, "Could not find declared class #{title}" else raise ArgumentError, "Invalid resource type #{type}" end end end def ref to_s end # Find our resource. def resolve catalog ? catalog.resource(to_s) : nil end # The resource's type implementation # @return [Puppet::Type, Puppet::Resource::Type] # @api private def resource_type @rstype ||= case type when "Class"; environment.known_resource_types.hostclass(title == :main ? "" : title) when "Node"; environment.known_resource_types.node(title) else Puppet::Type.type(type) || environment.known_resource_types.definition(type) 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 # Produce a simple hash of our parameters. def to_hash parse_title.merge parameters end def to_s "#{type}[#{title}]" end def uniqueness_key # Temporary kludge to deal with inconsistant use patters h = self.to_hash h[namevar] ||= h[:name] h[:name] ||= h[namevar] 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 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 "%s { '%s':\n%s}" % [self.type.to_s.downcase, self.title, 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 # Consult external data bindings for class parameter values which must be # namespaced in the backend. # # Example: # # class foo($port=0){ ... } # # We make a request to the backend for the key 'foo::port' not 'foo' # def lookup_external_default_for(param, scope) # Only lookup parameters for host classes return nil unless resource_type.type == :hostclass name = "#{resource_type.name}::#{param}" lookup_with_databinding(name, scope) end private :lookup_external_default_for def lookup_with_databinding(name, scope) begin Puppet::DataBinding.indirection.find( name, :environment => scope.environment, :variables => scope) rescue Puppet::DataBinding::LookupError => e raise Puppet::Error.new("Error from DataBinding '#{Puppet[:data_binding_terminus]}' while looking up '#{name}': #{e.message}", e) end end private :lookup_with_databinding def set_default_parameters(scope) return [] unless resource_type and resource_type.respond_to?(:arguments) unless is_a?(Puppet::Parser::Resource) fail Puppet::DevError, "Cannot evaluate default parameters for #{self} - not a parser resource" end missing_arguments.collect do |param, default| external_value = lookup_external_default_for(param, scope) if external_value.nil? && default.nil? next elsif external_value.nil? value = default.safeevaluate(scope) else value = external_value end self[param.to_sym] = value param end.compact end def copy_as_resource result = Puppet::Resource.new(type, title) result.file = self.file result.line = self.line result.exported = self.exported result.virtual = self.virtual result.tag(*self.tags) result.environment = environment result.instance_variable_set(:@rstype, resource_type) future_parser_not_in_use = !Puppet.future_parser?(result.environment) to_hash.each do |p, v| if v.is_a?(Puppet::Resource) v = Puppet::Resource.new(v.type, v.title) 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 = Puppet::Resource.new(av.type, av.title) if av.is_a?(Puppet::Resource) av end end if future_parser_not_in_use # !Puppet.future_parser? # If the value is an array with only one value, then # convert it to a single value. This is largely so that # the database interaction doesn't have to worry about # whether it returns an array or a string. # # This behavior is not done in the future parser, but we can't issue a # deprecation warning either since there isn't anything that a user can # do about it. result[p] = if v.is_a?(Array) and v.length == 1 v[0] else v end else result[p] = v end end result 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. def validate_complete 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 #{self}" unless parameters.include?(param) end # Perform optional type checking if Puppet.future_parser? # Perform 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 = Puppet::Pops::Types::TypeCalculator.generalize!(inferred_type) fail Puppet::ParseError, "Expected parameter '#{name}' of '#{self}' to have type #{t.to_s}, got #{actual.to_s}" end end end end def validate_parameter(name) raise ArgumentError, "Invalid parameter #{name}" unless valid_parameter?(name) end def prune_parameters(options = {}) 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 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? and t = resource_type and 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 def 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 ] elsif argtype.is_a?(Hash) then raise ArgumentError, "Puppet::Resource.new does not take a hash as the first argument. "+ "Did you mean (#{(argtype[:type] || argtype["type"]).inspect}, #{(argtype[:title] || argtype["title"]).inspect }) ?" else raise ArgumentError, "No title provided and #{argtype.inspect} is not a valid resource reference" end end def munge_type_name(value) return :main if value == :main return "Class" if value == "" or value.nil? or value.to_s.downcase == "component" value.to_s.split("::").collect { |s| s.capitalize }.join("::") end def parse_title h = {} type = resource_type if type.respond_to? :title_patterns 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}\"." else return { :name => title.to_s } end end def parameters # @parameters could have been loaded from YAML, causing it to be nil (by # bypassing initialize). @parameters ||= {} end end puppet-3.8.5/lib/puppet/resource/0000775005276200011600000000000012650174565016663 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/resource/catalog.rb0000664005276200011600000004133612650174557020632 0ustar jenkinsjenkinsrequire 'puppet/node' require 'puppet/indirector' require 'puppet/transaction' require 'puppet/util/pson' require 'puppet/util/tagging' require 'puppet/graph' # 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 extend Puppet::Util::Pson # 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 # 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 def title_key_for_ref( ref ) ref =~ /^([-\w:]+)\[(.*)\]$/m [$1, $2] 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) 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) 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) @resource_table[title_key] = resource @resources << 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) if resource.respond_to?(:isomorphic?) and resource.isomorphic? and resource.name != resource.title self.alias(resource, resource.uniqueness_key) end end private :create_resource_aliases # Create an alias for a resource. def alias(resource, key) resource.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 == resource.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 = " at #{resource.file}:#{resource.line}" if resource.file and resource.line existing_declaration = " at #{existing.file}:#{existing.line}" if existing.file and existing.line msg = "Cannot alias #{resource.ref} to #{key.inspect}#{resource_declaration}; resource #{newref.inspect} already declared#{existing_declaration}" raise ArgumentError, msg end @resource_table[newref] = resource @aliases[resource.ref] ||= [] @aliases[resource.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. # @option options [Array[String]] :tags # Tags used to filter the transaction. If supplied then only # resources tagged with any of these tags will be evaluated. # @option options [Boolean] :ignoreschedules # Ignore schedules when evaluating resources # @option options [Boolean] :for_network_device # Whether this catalog is for a network device # # @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 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}" 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) super() @name = name @classes = [] @resource_table = {} @resources = [] @relationship_graph = nil @host_config = true @environment_instance = environment @environment = environment.to_s @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| title_key = title_key_for_ref(resource.ref) @resource_table.delete(title_key) if aliases = @aliases[resource.ref] aliases.each { |res_alias| @resource_table.delete(res_alias) } @aliases.delete(resource.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) resource.remove end end # Look a resource up by its reference (e.g., File[/etc/passwd]). def resource(type, title = nil) # Always create a resource reference, so that it always # canonicalizes how we are referring to them. attributes = { :environment => environment_instance } if title res = Puppet::Resource.new(type, title, attributes) else # If they didn't provide a title, then we expect the first # argument to be of the form 'Class[name]', which our # Reference class canonicalizes for us. res = Puppet::Resource.new(nil, type, attributes) end res.catalog = self title_key = [res.type, res.title.to_s] uniqueness_key = [res.type, res.uniqueness_key].flatten @resource_table[title_key] || @resource_table[uniqueness_key] 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 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 #{edge.source.inspect}" end edge.source = source unless target = result.resource(edge.target) raise ArgumentError, "Could not intern from data: Could not find relationship target #{edge.target.inspect}" end edge.target = target result.add_edge(edge) end end if classes = data['classes'] result.add_class(*classes) end result end def self.from_pson(data) Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.") self.from_data_hash(data) end def to_data_hash { 'tags' => tags, 'name' => name, 'version' => version, 'environment' => environment.to_s, 'resources' => @resources.collect { |v| @resource_table[v].to_pson_data_hash }, 'edges' => edges. collect { |e| e.to_pson_data_hash }, 'classes' => classes } end PSON.register_document_type('Catalog',self) def to_pson_data_hash { 'document_type' => 'Catalog', 'data' => to_data_hash, 'metadata' => { 'api_version' => 1 } } end def to_pson(*args) to_pson_data_hash.to_pson(*args) 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 ::File.open(Puppet[:classfile], "w") do |f| f.puts classes.join("\n") end rescue => detail Puppet.err "Could not create class file #{Puppet[:classfile]}: #{detail}" end # Store the list of resources we manage def write_resource_file ::File.open(Puppet[:resourcefile], "w") do |f| to_print = resources.map do |resource| next unless resource.managed? if resource.name_var "#{resource.type}[#{resource[resource.name_var]}]" else "#{resource.ref.downcase}" end end.compact f.puts to_print.join("\n") end rescue => detail Puppet.err "Could not create resource file #{Puppet[:resourcefile]}: #{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 #{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 = 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 msg = "Duplicate declaration: #{resource.ref} is already declared" msg << " in file #{existing_resource.file}:#{existing_resource.line}" if existing_resource.file and existing_resource.line msg << "; cannot redeclare" 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 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 #{edge.source.ref} when converting #{message} resources" end unless target = map[edge.target.ref] raise Puppet::DevError, "Could not find resource #{edge.target.ref} when converting #{message} resources" end result.add_edge(source, target, edge.label) end map.clear result.add_class(*self.classes) result.tag(*self.tags) result end def virtual_not_exported?(resource) resource.virtual && !resource.exported end end puppet-3.8.5/lib/puppet/resource/status.rb0000664005276200011600000001105112650174557020532 0ustar jenkinsjenkinsrequire 'time' require 'puppet/network/format_support' module Puppet class Resource class Status include Puppet::Util::Tagging include Puppet::Util::Logging include Puppet::Network::FormatSupport attr_accessor :resource, :node, :file, :line, :current_values, :status, :evaluation_time STATES = [:skipped, :failed, :failed_to_restart, :restarted, :changed, :out_of_sync, :scheduled] attr_accessor *STATES attr_reader :source_description, :containment_path, :default_log_level, :time, :resource, :change_count, :out_of_sync_count, :resource_type, :title YAML_ATTRIBUTES = %w{@resource @file @line @evaluation_time @change_count @out_of_sync_count @tags @time @events @out_of_sync @changed @resource_type @title @skipped @failed @containment_path}. map(&:to_sym) def self.from_data_hash(data) obj = self.allocate obj.initialize_from_hash(data) obj end def self.from_pson(data) Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.") self.from_data_hash(data) 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 end def events @events end def failed_because(detail) @real_resource.log_exception(detail, "Could not evaluate: #{detail}") failed = true # 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. add_event(@real_resource.event(:name => :resource_error, :status => "failure", :message => detail.to_s)) 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 @file = resource.file @line = resource.line tag(*resource.tags) @time = Time.now @events = [] @resource_type = resource.type.to_s.capitalize @title = resource.title end def initialize_from_hash(data) @resource_type = data['resource_type'] @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'] @events = data['events'].map do |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, 'containment_path' => @containment_path, 'evaluation_time' => @evaluation_time, 'tags' => @tags, 'time' => @time.iso8601(9), 'failed' => @failed, 'changed' => @changed, 'out_of_sync' => @out_of_sync, 'skipped' => @skipped, 'change_count' => @change_count, 'out_of_sync_count' => @out_of_sync_count, 'events' => @events, } end def to_yaml_properties YAML_ATTRIBUTES & super end private def log_source source_description end end end end puppet-3.8.5/lib/puppet/resource/type.rb0000664005276200011600000003400012650174557020167 0ustar jenkinsjenkinsrequire 'puppet/parser' require 'puppet/util/warnings' require 'puppet/util/errors' require 'puppet/util/inline_docs' require 'puppet/parser/ast/leaf' require 'puppet/parser/ast/block_expression' require 'puppet/dsl' # Puppet::Resource::Type represents nodes, classes and defined types. # # It has a standard format for external consumption, usable from the # resource_type indirection via rest and the resource_type face. See the # {file:api_docs/http_resource_type.md#Schema resource type schema # description}. # # @api public class Puppet::Resource::Type Puppet::ResourceType = self include Puppet::Util::InlineDocs include Puppet::Util::Warnings include Puppet::Util::Errors RESOURCE_KINDS = [:hostclass, :node, :definition] # Map the names used in our documentation to the names used internally RESOURCE_KINDS_TO_EXTERNAL_NAMES = { :hostclass => "class", :node => "node", :definition => "defined_type", } RESOURCE_EXTERNAL_NAMES_TO_KINDS = RESOURCE_KINDS_TO_EXTERNAL_NAMES.invert attr_accessor :file, :line, :doc, :code, :ruby_code, :parent, :resource_type_collection attr_reader :namespace, :arguments, :behaves_like, :module_name # Map from argument (aka parameter) names to Puppet Type # @return [Hash :parser def self.from_data_hash(data) name = data.delete('name') or raise ArgumentError, "Resource Type names must be specified" kind = data.delete('kind') || "definition" unless type = RESOURCE_EXTERNAL_NAMES_TO_KINDS[kind] raise ArgumentError, "Unsupported resource kind '#{kind}'" end data = data.inject({}) { |result, ary| result[ary[0].intern] = ary[1]; result } # External documentation uses "parameters" but the internal name # is "arguments" data[:arguments] = data.delete(:parameters) new(type, name, data) end def self.from_pson(data) Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.") self.from_data_hash(data) end def to_data_hash data = [:doc, :line, :file, :parent].inject({}) do |hash, param| next hash unless (value = self.send(param)) and (value != "") hash[param.to_s] = value hash end # External documentation uses "parameters" but the internal name # is "arguments" # Dump any arguments as source data['parameters'] = Hash[arguments.map do |k,v| [k, v.respond_to?(:source_text) ? v.source_text : v] end] data['name'] = name unless RESOURCE_KINDS_TO_EXTERNAL_NAMES.has_key?(type) raise ArgumentError, "Unsupported resource kind '#{type}'" end data['kind'] = RESOURCE_KINDS_TO_EXTERNAL_NAMES[type] data 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 # 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(:namespace => namespace, :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 if code if @match # Only bother setting up the ephemeral scope if there are match variables to add into it begin elevel = scope.ephemeral_level scope.ephemeral_from(@match, file, line) code.safeevaluate(scope) ensure scope.unset_ephemeral_var(elevel) end else code.safeevaluate(scope) end end evaluate_ruby_code(resource, scope) if ruby_code end def initialize(type, name, options = {}) @type = type.to_s.downcase.to_sym raise ArgumentError, "Invalid resource supertype '#{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 # 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" unless type == :hostclass fail "#{other.name} is not a class; cannot add code from it" unless other.type == :hostclass fail "Cannot have code outside of a class/node/define because 'freeze_main' is enabled" if name == "" and Puppet.settings[:freeze_main] if parent and other.parent and parent != other.parent fail "Cannot merge classes with different parent classes (#{name} => #{parent} vs. #{other.name} => #{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]) # self.code = self.code.sequence_with(other.code) 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) type == :definition and raise ArgumentError, "Cannot create resources for defined resource types" resource_type = type == :hostclass ? :class : :node # 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 resource = scope.catalog.resource(resource_type, name) and !parameters return resource end resource = Puppet::Parser::Resource.new(resource_type, name, :scope => scope, :source => self) assign_parameter_values(parameters, resource) 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.tag(*resource.tags) end end def name return @name unless @name.is_a?(Regexp) @name.source.downcase.gsub(/[^-\w:.]/,'').sub(/^\.+/,'') end def name_is_regex? @name.is_a?(Regexp) end def assign_parameter_values(parameters, resource) 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 # MQR TODO: # # The change(s) introduced by the fix for #4270 are mostly silly & should be # removed, though we didn't realize it at the time. If it can be established/ # ensured that nodes never call parent_type and that resource_types are always # (as they should be) members of exactly one resource_type_collection the # following method could / should be replaced with: # # def parent_type # @parent_type ||= parent && ( # resource_type_collection.find_or_load([name],parent,type.to_sym) || # fail Puppet::ParseError, "Could not find parent resource type '#{parent}' of type #{type} in #{resource_type_collection.environment}" # ) # end # # ...and then the rest of the changes around passing in scope reverted. # def parent_type(scope = nil) return nil unless parent unless @parent_type raise "Must pass scope to parent_type when called first time" unless scope unless @parent_type = scope.environment.known_resource_types.send("find_#{type}", [name], parent) fail Puppet::ParseError, "Could not find parent resource type '#{parent}' of type #{type} in #{scope.environment}" end end @parent_type end # Set any arguments passed by the resource as variables in the scope. def set_resource_parameters(resource, scope) set = {} resource.to_hash.each do |param, value| param = param.to_sym fail Puppet::ParseError, "#{resource.ref} does not accept attribute #{param}" unless valid_parameter?(param) exceptwrap { scope[param.to_s] = value } set[param] = true end if @type == :hostclass scope["title"] = resource.title.to_s.downcase unless set.include? :title scope["name"] = resource.name.to_s.downcase unless set.include? :name else scope["title"] = resource.title unless set.include? :title scope["name"] = resource.name unless set.include? :name end scope["module_name"] = module_name if module_name and ! set.include? :module_name if caller_name = scope.parent_module_name and ! set.include?(:caller_module_name) scope["caller_module_name"] = caller_name end scope.class_set(self.name,scope) if hostclass? or node? # Evaluate the default parameters, now that all other variables are set default_params = resource.set_default_parameters(scope) default_params.each { |param| scope[param] = resource[param] } # This has to come after the above parameters so that default values # can use their values resource.validate_complete end # Check whether a given argument is valid. def valid_parameter?(param) param = param.to_s return true if param == "name" return true if Puppet::Type.metaparam?(param) return false unless defined?(@arguments) return(arguments.include?(param) ? true : false) end def set_arguments(arguments) @arguments = {} 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. Only supported when parser == "future" # def set_argument_types(name_to_type_hash) @argument_types = {} # Stop here if not running under future parser, the rest requires pops to be initialized # and that the type system is available return unless Puppet.future_parser? && 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." 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 #{t.class}" end @argument_types[name] = t end end private def convert_from_ast(name) value = name.value if value.is_a?(Puppet::Parser::AST::Regex) name = value.value else name = 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 def evaluate_ruby_code(resource, scope) Puppet::DSL::ResourceAPI.new(resource, scope, ruby_code).evaluate end # Split an fq name into a namespace and name def namesplit(fullname) ary = fullname.split("::") n = ary.pop || "" ns = ary.join("::") return ns, n end def parent_scope(scope, klass) scope.class_scope(klass) || raise(Puppet::DevError, "Could not find scope for #{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, ignored_shortname = @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 #{self.name} definition" else raise Puppet::ParseError, "#{param} is a metaparameter; please choose another parameter name in the #{self.name} definition" end end end puppet-3.8.5/lib/puppet/resource/type_collection.rb0000664005276200011600000001354112650174557022411 0ustar jenkinsjenkinsrequire 'puppet/parser/type_loader' require 'puppet/util/file_watcher' require 'puppet/util/warnings' class Puppet::Resource::TypeCollection attr_reader :environment attr_accessor :parse_failed include Puppet::Util::Warnings def clear @hostclasses.clear @definitions.clear @nodes.clear @watched_files.clear @notfound.clear end def initialize(env) @environment = env @hostclasses = {} @definitions = {} @nodes = {} @notfound = {} # So we can keep a list and match the first-defined regex @node_list = [] @watched_files = Puppet::Util::FileWatcher.new 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 }.inspect end def <<(thing) add(thing) self end def add(instance) if instance.type == :hostclass and other = @hostclasses[instance.name] and other.type == :hostclass other.merge(instance) return other end method = "add_#{instance.type}" send(method, instance) instance.resource_type_collection = self instance end def add_hostclass(instance) dupe_check(instance, @hostclasses) { |dupe| "Class '#{instance.name}' is already defined#{dupe.error_context}; cannot redefine" } dupe_check(instance, @definitions) { |dupe| "Definition '#{instance.name}' is already defined#{dupe.error_context}; cannot be redefined as a class" } @hostclasses[instance.name] = instance instance end def hostclass(name) @hostclasses[munge_name(name)] end def add_node(instance) dupe_check(instance, @nodes) { |dupe| "Node '#{instance.name}' is already defined#{dupe.error_context}; cannot redefine" } @node_list << instance @nodes[instance.name] = instance instance 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 |node| next unless node.name_is_regex? return node if node.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| "'#{instance.name}' is already defined#{dupe.error_context} as a class; cannot redefine as a definition" } dupe_check(instance, @definitions) { |dupe| "Definition '#{instance.name}' is already defined#{dupe.error_context}; cannot be redefined" } @definitions[instance.name] = instance end def definition(name) @definitions[munge_name(name)] end def find_node(namespaces, name) @nodes[munge_name(name)] end def find_hostclass(namespaces, name, options = {}) find_or_load(namespaces, name, :hostclass, options) end def find_definition(namespaces, name) find_or_load(namespaces, name, :definition) end [:hostclasses, :nodes, :definitions].each do |m| define_method(m) do instance_variable_get("@#{m}").dup end end def require_reparse? @parse_failed || stale? end def stale? @watched_files.changed? 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]).strip end end @version rescue Puppet::ExecutionFailure => e raise Puppet::ParseError, "Execution of config_version command `#{environment.config_version}` failed: #{e.message}", e.backtrace end def watch_file(filename) @watched_files.watch(filename) end def watching_file?(filename) @watched_files.watching?(filename) end private # Return a list of all possible fully-qualified names that might be # meant by the given name, in the context of namespaces. def resolve_namespaces(namespaces, name) name = name.downcase if name =~ /^::/ # name is explicitly fully qualified, so just return it, sans # initial "::". return [name.sub(/^::/, '')] end if name == "" # The name "" has special meaning--it always refers to a "main" # hostclass which contains all toplevel resources. return [""] end namespaces = [namespaces] unless namespaces.is_a?(Array) namespaces = namespaces.collect { |ns| ns.downcase } result = [] namespaces.each do |namespace| ary = namespace.split("::") # Search each namespace nesting in innermost-to-outermost order. while ary.length > 0 result << "#{ary.join("::")}::#{name}" ary.pop end # Finally, search the toplevel namespace. result << name end return result.uniq end # Resolve namespaces and find the given object. Autoload it if # necessary. def find_or_load(namespaces, name, type, options = {}) searchspace = options[:assume_fqname] ? [name].flatten : resolve_namespaces(namespaces, name) searchspace.each do |fqname| result = send(type, fqname) unless result if @notfound[fqname] and 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. debug_once "Not attempting to load #{type} #{fqname} as this object was missing during a prior compilation" else result = loader.try_load_fqname(type, fqname) @notfound[fqname] = result.nil? end end return result if result end return nil 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 end puppet-3.8.5/lib/puppet/resource/type_collection_helper.rb0000664005276200011600000000024312650174557023743 0ustar jenkinsjenkinsrequire 'puppet/resource/type_collection' module Puppet::Resource::TypeCollectionHelper def known_resource_types environment.known_resource_types end end puppet-3.8.5/lib/puppet/run.rb0000664005276200011600000000413212650174557016166 0ustar jenkinsjenkinsrequire 'puppet/agent' require 'puppet/configurer' require 'puppet/indirector' # A basic class for running the agent. Used by # `puppet kick` to kick off agents remotely. class Puppet::Run extend Puppet::Indirector indirects :run, :terminus_class => :local attr_reader :status, :background, :options def agent # Forking disabled for "puppet kick" runs Puppet::Agent.new(Puppet::Configurer, false) end def background? background end def initialize(options = {}) if options.include?(:background) @background = options[:background] options.delete(:background) end valid_options = [:tags, :ignoreschedules, :pluginsync] options.each do |key, value| raise ArgumentError, "Run does not accept #{key}" unless valid_options.include?(key) end @options = options end def initialize_from_hash(hash) @options = {} hash['options'].each do |key, value| @options[key.to_sym] = value end @background = hash['background'] @status = hash['status'] end def log_run msg = "" msg += "triggered run" % if options[:tags] msg += " with tags #{options[:tags].inspect}" end msg += " ignoring schedules" if options[:ignoreschedules] Puppet.notice msg end def run if agent.running? @status = "running" return self end log_run if background? Thread.new { agent.run(options) } else agent.run(options) end @status = "success" self end def self.from_hash(hash) obj = allocate obj.initialize_from_hash(hash) obj end def self.from_data_hash(data) if data['options'] return from_hash(data) end options = { :pluginsync => Puppet[:pluginsync] } data.each do |key, value| options[key.to_sym] = value end new(options) end def self.from_pson(hash) Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.") self.from_data_hash(hash) end def to_data_hash { :options => @options, :background => @background, :status => @status } end end puppet-3.8.5/lib/puppet/scheduler.rb0000664005276200011600000000063612650174557017345 0ustar jenkinsjenkinsmodule 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-3.8.5/lib/puppet/scheduler/0000775005276200011600000000000012650174565017012 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/scheduler/job.rb0000664005276200011600000000156012650174557020114 0ustar jenkinsjenkinsmodule 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-3.8.5/lib/puppet/scheduler/scheduler.rb0000664005276200011600000000202312650174557021313 0ustar jenkinsjenkinsmodule 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-3.8.5/lib/puppet/scheduler/splay_job.rb0000664005276200011600000000104512650174557021322 0ustar jenkinsjenkinsmodule 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-3.8.5/lib/puppet/scheduler/timer.rb0000664005276200011600000000025512650174557020462 0ustar jenkinsjenkinsmodule Puppet::Scheduler class Timer def wait_for(seconds) if seconds > 0 sleep(seconds) end end def now Time.now end end end puppet-3.8.5/lib/puppet/settings.rb0000664005276200011600000013346212650174557017233 0ustar jenkinsjenkinsrequire 'puppet' require 'getoptlong' require 'puppet/util/watched_file' require 'puppet/util/command_line/puppet_option_parser' require 'forwardable' # 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/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' # 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] # 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, :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["hostname"].value end def self.domain_fact() Facter["domain"].value 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 # @param name [Symbol] The name of the setting to fetch # @return [Puppet::Settings::BaseSetting] The setting object def setting(name) @config[name] 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 # Clear the list of environments, because they cache, at least, the module path. # We *could* preferentially just clear them if the modulepath is changed, # but we don't really know if, say, the vardir is changed and the modulepath # is defined relative to it. We need the defined?(stuff) because of loading # order issues. Puppet::Node::Environment.clear if defined?(Puppet::Node) and defined?(Puppet::Node::Environment) 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 @app_defaults_initialized = true end 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, 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_delegator :@config, :each # Iterate over each section name. def eachsection yielded = [] @config.each do |name, object| section = object.section unless yielded.include? section yield section yielded << section end end end # Return an object by name. 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 # check to see if a short name is already defined def shortinclude?(short) short = short.intern if name.is_a? String @shortnames.include?(short) 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 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, val| puts "#{name} = #{val}" 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 # Return a given object's file metadata. def metadata(param) if obj = @config[param.to_sym] and obj.is_a?(FileSetting) { :owner => obj.owner, :group => obj.group, :mode => obj.mode }.delete_if { |key, value| value.nil? } else nil end end # Make a directory with the appropriate user, group, and mode def mkdir(default) obj = get_config_file_default(default) Puppet::Util::SUIDManager.asuser(obj.owner, obj.group) do mode = obj.mode || 0750 Dir.mkdir(obj.value, mode) end 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 # Return all of the settings associated with a given section. def params(section = nil) if section section = section.intern if section.is_a? String @config.find_all { |name, obj| obj.section == section }.collect { |name, obj| name } else @config.keys end end def parse_config(text, file = "text") begin data = @config_file_parser.parse_file(file, text) 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) 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.reverse.each do |source| source = preferred_run_mode if source == :run_mode if section = @configuration_file.sections[source] 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, :priority => PrioritySetting, :autosign => AutosignSetting, } # 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}'" 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 # The order in which to search for values. def searchpath(environment = nil) [:memory, :cli, environment, :run_mode, :main, :application_defaults, :overridden_defaults].compact end # Get a list of objects per section def sectionlist sectionlist = [] self.each { |name, obj| section = obj.section || "puppet" sections[section] ||= [] sectionlist << section unless sectionlist.include?(section) sections[section] << obj } return sectionlist, sections 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 def set_value(param, value, type, options = {}) Puppet.deprecation_warning("Puppet.settings.set_value is deprecated. Use Puppet[]= instead.") if @value_sets[type] @value_sets[type].set(param, value) unsafe_flush_cache end end # Deprecated; use #define_settings instead def setdefaults(section, defs) Puppet.deprecation_warning("'setdefaults' is deprecated and will be removed; please call 'define_settings' instead") define_settings(section, defs) 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, respresented # 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!" unless hash.is_a? Hash name = name.to_sym hash[:name] = name hash[:section] = section raise ArgumentError, "Setting #{name} is already defined" if @config.include?(name) tryconfig = newsetting(hash) if short = tryconfig.short if other = @shortnames[short] raise ArgumentError, "Setting #{other.name} is already using short name '#{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| next if (key == :manifestdir && should_skip_manifestdir?()) file = @config[key] 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 def should_skip_manifestdir?() setting = @config[:environmentpath] !(setting.nil? || setting.value.nil? || setting.value.empty?) end private :should_skip_manifestdir? # 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? 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 def uninterpolated_value(param, environment = nil) Puppet.deprecation_warning("Puppet.settings.uninterpolated_value is deprecated. Use Puppet.settings.value instead") param = param.to_sym environment &&= environment.to_sym values(environment, self.preferred_run_mode).lookup(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) param = param.to_sym environment &&= environment.to_sym setting = @config[param] # Short circuit to nil for undefined settings. return nil if setting.nil? # Check the cache first. It needs to be a per-environment # cache so that we don't spread values from one env # to another. if @cache[environment||"none"].has_key?(param) return @cache[environment||"none"][param] elsif bypass_interpolation val = values(environment, self.preferred_run_mode).lookup(param) else val = values(environment, self.preferred_run_mode).interpolate(param) end @cache[environment||"none"][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/puppet/puppet.conf) # 3. Otherwise, use the user puppet.conf (usually ~/.puppet/puppet.conf) # # @api private # @todo this code duplicates {Puppet::Util::RunMode#which_dir} as described # in {http://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) @config_file_parser.parse_file(file, read_file(file)) end private DEPRECATION_REFS = { [:manifest, :modulepath, :config_version, :templatedir, :manifestdir] => "See http://links.puppetlabs.com/env-settings-deprecations" }.freeze # 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) conf_sections = puppet_conf.sections.inject([]) do |accum,entry| accum << entry[1] if [:main, :master, :agent, :user].include?(entry[0]) accum end conf_sections.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? Puppet.deprecation_warning("Setting #{name} is deprecated. #{ref}", "setting-#{name}") when setting.allowed_on_commandline? Puppet.deprecation_warning("Setting #{name} is deprecated in puppet.conf. #{ref}", "puppet-conf-setting-#{name}") end end def get_config_file_default(default) obj = nil unless obj = @config[default] raise ArgumentError, "Unknown default #{default}" end raise ArgumentError, "Default #{default} is not a file" unless obj.is_a? FileSetting obj 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) catalog.add_resource( Puppet::Resource.new(:file, configured_environment_path, :parameters => { :ensure => 'directory' }) ) 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).collect do |name| case name when :cli, :memory, :application_defaults, :overridden_defaults @value_sets[name] when :run_mode if @configuration_file section = @configuration_file.sections[mode] if section ValuesFromSection.new(mode, section) end end else values_from_section = nil if @configuration_file if section = @configuration_file.sections[name] values_from_section = ValuesFromSection.new(name, section) end end if values_from_section.nil? && global_defaults_initialized? values_from_section = ValuesFromEnvironmentConf.new(name) end values_from_section end end.compact end # Read the file in. # @api private def read_file(file) return Puppet::FileSystem.read(file) 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 |set| 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] if setting 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}': #{err}", err.backtrace end setting.munge(val) end else nil end end private 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}" end else failed_environment_interpolation = true expression end interpolated_expression end if failed_environment_interpolation Puppet.warning("You cannot interpolate $environment within '#{setting_name}' when using directory environments. Its value will remain #{interpolated_value}.") end interpolated_value else value end end def ok_to_interpolate_environment(setting_name) return true if Puppet.settings.value(:environmentpath, nil, true).empty? ENVIRONMENT_INTERPOLATION_ALLOWED.include?(setting_name.to_s) end end class Values extend Forwardable 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.inspect}" end if default.has_hook? default.handle(value) end @values[name] = value end end class ValuesFromSection 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 end # @api private class ValuesFromEnvironmentConf def initialize(environment_name) @environment_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) environments.get_conf(@environment_name) end end end end puppet-3.8.5/lib/puppet/settings/0000775005276200011600000000000012650174565016674 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/settings/array_setting.rb0000664005276200011600000000046712650174557022104 0ustar jenkinsjenkinsclass 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 #{value.class}" end end end puppet-3.8.5/lib/puppet/settings/autosign_setting.rb0000664005276200011600000000124312650174557022610 0ustar jenkinsjenkinsrequire '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" end end end puppet-3.8.5/lib/puppet/settings/base_setting.rb0000664005276200011600000001356012650174557021676 0ustar jenkinsjenkinsrequire 'puppet/settings/errors' # The base setting type class Puppet::Settings::BaseSetting attr_accessor :name, :desc, :section, :default, :call_on_define, :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_on_define Puppet.deprecation_warning "call_on_define has been deprecated. Please use call_hook_on_define?" call_hook_on_define? end def call_on_define=(value) if value Puppet.deprecation_warning ":call_on_define has been changed to :call_hook => :on_define_and_write. Please change #{name}." @call_hook = :on_define_and_write else Puppet.deprecation_warning ":call_on_define => :false has been changed to :call_hook => :on_write_only. Please change #{name}." @call_hook = :on_write_only end end def call_hook=(value) if value.nil? Puppet.warning "Setting :#{name} :call_hook is nil, defaulting to :on_write_only" value ||= :on_write_only end raise ArgumentError, "Invalid option #{value} for call_hook" unless self.class.available_call_hook_values.include? value @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 #added as a proper method, only to generate a deprecation warning #and return value from def setbycli Puppet.deprecation_warning "Puppet.settings.setting(#{name}).setbycli is deprecated. Use Puppet.settings.set_by_cli?(#{name}) instead." @settings.set_by_cli?(name) end def setbycli=(value) Puppet.deprecation_warning "Puppet.settings.setting(#{name}).setbycli= is deprecated. You should not manually set that values were specified on the command line." @settings.set_value(name, @settings[name], :cli) if value raise ArgumentError, "Cannot unset setbycli" unless value 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 raise ArgumentError, "Cannot reference :call_hook for :#{@name} if no :hook is defined" if args[:call_hook] and not args[:hook] args.each do |param, value| method = param.to_s + "=" raise ArgumentError, "#{self.class} (setting '#{args[:name]}') does not accept #{param}" unless self.respond_to? method self.send(method, value) end raise ArgumentError, "You must provide a description for the #{self.name} config option" unless self.desc end def iscreated @iscreated = true end def iscreated? @iscreated end # short name for the celement 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 @default = @default.call end return @default unless check_application_defaults_first return @settings.value(name, :application_defaults, true) || @default 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 def set_meta(meta) Puppet.notice("#{name} does not support meta data. Ignoring.") end def deprecated=(deprecation) raise(ArgumentError, "'#{deprecation}' is an unknown setting deprecation state. Must be either :completely or :allowed_on_commandline") unless [:completely, :allowed_on_commandline].include?(deprecation) @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 end puppet-3.8.5/lib/puppet/settings/boolean_setting.rb0000664005276200011600000000144412650174557022401 0ustar jenkinsjenkins# 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.inspect}' for boolean parameter: #{@name}" end end def type :boolean end end puppet-3.8.5/lib/puppet/settings/config_file.rb0000664005276200011600000000763112650174557021475 0ustar jenkinsjenkinsrequire 'puppet/settings/ini_file' ## # @api private # # Parses puppet configuration files # class Puppet::Settings::ConfigFile ALLOWED_SECTION_NAMES = ['main', 'master', 'agent', 'user'].freeze ## # @param value_converter [Proc] a function that will convert strings into ruby types # def initialize(value_converter) @value_converter = value_converter end def parse_file(file, text) result = Conf.new ini = Puppet::Settings::IniFile.parse(StringIO.new(text)) unique_sections_in(ini, file).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 #{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) ini.section_lines.collect do |section| if section.name == "application_defaults" || section.name == "global_defaults" raise Puppet::Error, "Illegal section '#{section.name}' in config file #{file} at line #{section.line_number}" end if !ALLOWED_SECTION_NAMES.include?(section.name) Puppet.deprecation_warning("Sections other than #{ALLOWED_SECTION_NAMES.join(', ')} are deprecated in puppet.conf. Please use the directory environments feature to specify environments. (See http://docs.puppetlabs.com/puppet/latest/reference/environments.html)") 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\d]+)\s*$/ param, value = $1.intern, $2 result[param] = value raise ArgumentError, "Invalid file option '#{param}'" unless [:owner, :mode, :group].include?(param) if param == :mode and value !~ /^\d+$/ raise ArgumentError, "File modes must be numbers" end else raise ArgumentError, "Could not parse '#{string}'" end end '' end result[:value] = value.sub(/\s*$/, '') result end end puppet-3.8.5/lib/puppet/settings/directory_setting.rb0000664005276200011600000000043312650174557022763 0ustar jenkinsjenkinsclass Puppet::Settings::DirectorySetting < Puppet::Settings::FileSetting def type :directory end # @api private def open_file(filename, option = 'r', &block) controlled_access do |mode| Puppet::FileSystem.open(filename, mode, option, &block) end end end puppet-3.8.5/lib/puppet/settings/duration_setting.rb0000664005276200011600000000170612650174557022610 0ustar jenkinsjenkins# 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 when (value.is_a?(String) and value =~ FORMAT) $1.to_i * UNITMAP[$2 || 's'] else raise Puppet::Settings::ValidationError, "Invalid duration format '#{value.inspect}' for parameter: #{@name}" end end end puppet-3.8.5/lib/puppet/settings/enum_setting.rb0000664005276200011600000000054712650174557021731 0ustar jenkinsjenkinsclass 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 '#{values.join("', '")}'" end end end puppet-3.8.5/lib/puppet/settings/environment_conf.rb0000664005276200011600000001413712650174557022601 0ustar jenkinsjenkins# Configuration settings for a single directory Environment. # @api private class Puppet::Settings::EnvironmentConf VALID_SETTINGS = [:modulepath, :manifest, :config_version, :environment_timeout, :parser].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] 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') config = nil 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, parser, environment_timeout = 0) Static.new(environment, environment_timeout, parser) 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 errmsg = ["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_manifest}' which does not match", "the default_manifest setting '#{puppet_conf_manifest}'. If this", "environment is expecting to find modules in", "'#{environment_conf_manifest}', they will not be available!"] Puppet.err(errmsg.join(' ')) 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 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| absolute(p) }.join(File::PATH_SEPARATOR) end end def parser get_setting(:parser, Puppet.settings.value(:parser)) 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 Puppet.warning("Invalid sections in environment.conf at '#{path_to_conf_file}'. Environment conf may not have sections. The following sections are being ignored: '#{(section_keys - [:main]).join(',')}'") valid = false end extraneous_settings = main.settings.map(&:name) - VALID_SETTINGS if !extraneous_settings.empty? Puppet.warning("Invalid settings in environment.conf at '#{path_to_conf_file}'. The following unknown setting(s) are being ignored: #{extraneous_settings.join(', ')}") valid = false end return valid end def get_setting(setting_name, default = nil) value = raw_setting(setting_name) value ||= default yield value end def absolute(path) return nil if path.nil? if path =~ /^\$/ # Path begins with $something interpolatable path else File.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 :parser def initialize(environment, environment_timeout, parser) @environment = environment @environment_timeout = environment_timeout @parser = parser 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-3.8.5/lib/puppet/settings/errors.rb0000664005276200011600000000046212650174557020540 0ustar jenkinsjenkins# 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-3.8.5/lib/puppet/settings/file_or_directory_setting.rb0000664005276200011600000000133112650174557024460 0ustar jenkinsjenkinsclass 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 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-3.8.5/lib/puppet/settings/file_setting.rb0000664005276200011600000001457012650174557021705 0ustar jenkinsjenkins# 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" end } end # @api private def exclusive_open(option = 'r', &block) controlled_access do |mode| Puppet::FileSystem.exclusive_open(file(), mode, option, &block) end end # @api private 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}'" 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-3.8.5/lib/puppet/settings/ini_file.rb0000664005276200011600000000671512650174557021011 0ustar jenkinsjenkins# @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 when /^(\s*)\[(\w+)\](\s*)$/ config.append(SectionLine.new($1, $2, $3)) when /^(\s*)(\w+)(\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 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 write(fh) 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 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 write(fh) fh.puts(text) end end SettingLine = Struct.new(:prefix, :name, :infix, :value, :suffix) do include LineNumber def write(fh) fh.write(prefix) fh.write(name) fh.write(infix) fh.write(value) fh.puts(suffix) end end SectionLine = Struct.new(:prefix, :name, :suffix) do include LineNumber def write(fh) fh.write(prefix) fh.write("[") fh.write(name) fh.write("]") fh.puts(suffix) end end class DefaultSection < SectionLine def initialize super("", DEFAULT_SECTION_NAME, "") end def write(fh) end end end puppet-3.8.5/lib/puppet/settings/path_setting.rb0000664005276200011600000000037312650174557021716 0ustar jenkinsjenkinsclass 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-3.8.5/lib/puppet/settings/priority_setting.rb0000664005276200011600000000224712650174557022645 0ustar jenkinsjenkinsrequire '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.inspect}' for parameter: #{@name}" end end end puppet-3.8.5/lib/puppet/settings/string_setting.rb0000664005276200011600000000025212650174557022264 0ustar jenkinsjenkinsclass Puppet::Settings::StringSetting < Puppet::Settings::BaseSetting def type :string end def validate(value) value.nil? or value.is_a?(String) end end puppet-3.8.5/lib/puppet/settings/terminus_setting.rb0000664005276200011600000000046312650174557022630 0ustar jenkinsjenkinsclass 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}" end end end puppet-3.8.5/lib/puppet/settings/ttl_setting.rb0000664005276200011600000000274412650174557021571 0ustar jenkinsjenkins# 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 INFINITY = 1.0 / 0.0 # 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 # 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.inspect} - did you mean 'unlimited'?" end value when value == 'unlimited' 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.inspect}' for parameter: #{param_name}" end end end puppet-3.8.5/lib/puppet/settings/value_translator.rb0000664005276200011600000000062012650174557022605 0ustar jenkinsjenkins# 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-3.8.5/lib/puppet/ssl.rb0000664005276200011600000000053412650174557016165 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-3.8.5/lib/puppet/ssl/0000775005276200011600000000000012650174565015635 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/ssl/base.rb0000664005276200011600000000675412650174557017111 0ustar jenkinsjenkinsrequire '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, "#{self} has not declared what class it wraps") unless defined?(@wrapped_class) @wrapped_class end def self.validate_certname(name) raise "Certname #{name.inspect} must not contain unprintable or non-ASCII characters" 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, "#{self.class} did not override 'generate'" 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) raise ArgumentError, "Object must be an instance of #{wrapped_class}, #{instance.class} given" unless instance.is_a? wrapped_class raise ArgumentError, "Name must be supplied if it cannot be determined from the instance" if name.nil? and !instance.respond_to?(:subject) 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) @content = wrapped_class.new(File.read(path)) 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}'" end end private def wrapped_class self.class.wrapped_class end end puppet-3.8.5/lib/puppet/ssl/certificate.rb0000664005276200011600000000413512650174557020450 0ustar jenkinsjenkinsrequire '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 => < 'pp_uuid', 'value' => 'abcd'}] # # @return [Array 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 { |ext| {'oid' => ext.oid, 'value' => ext.value} } end end puppet-3.8.5/lib/puppet/ssl/certificate_authority.rb0000664005276200011600000004136512650174557022566 0ustar jenkinsjenkinsrequire '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.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}" 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, !!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, false, 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 Puppet.settings.setting(:capass).open('w') { |f| f.print pass } rescue Errno::EACCES => detail raise Puppet::Error, "Could not write CA password: #{detail}", detail.backtrace end @password = pass pass end # Lists the names of all signed certificates. # # @param name [Array] filter to cerificate names # # @return [Array] def list(name='*') list_certificates(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 # @api Puppet Enterprise Licensing # # @param name [Array] filter to cerificate names # # @return [Array] def list_certificates(name='*') 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 Puppet.settings.setting(:serial).exclusive_open('a+') 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}" 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, allow_dns_alt_names = false, self_signing_csr = nil) # This is a self-signed certificate 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 allow_dns_alt_names = true if hostname == Puppet[:certname].downcase unless csr = Puppet::SSL::CertificateRequest.indirection.find(hostname) raise ArgumentError, "Could not find certificate request for #{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, allow_dns_alt_names) 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}" # 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, allow_dns_alt_names) # 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) 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}" 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 #{cn.inspect} does not match expected certname #{hostname.inspect}" end if hostname !~ Puppet::SSL::Base::VALID_CERTNAME raise CertificateSigningError.new(hostname), "CSR #{hostname.inspect} subject contains unprintable or non-ASCII characters" 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: #{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 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 allow_dns_alt_names raise CertificateSigningError.new(hostname), "CSR '#{csr.name}' contains subject alternative names (#{csr.subject_alt_names.join(', ')}), which are disallowed. Use `puppet cert --allow-dns-alt-names sign #{csr.name}` to sign this request." 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.name}' contains a subjectAltName outside the DNS label space: #{csr.subject_alt_names.join(', ')}. To continue, this CSR needs to be cleaned." end # Check for wildcards in the subjectAltName fields too. if csr.subject_alt_names.any? {|x| x.include? '*' } raise CertificateSigningError.new(hostname), "CSR '#{csr.name}' subjectAltName contains a wildcard, which is not allowed: #{csr.subject_alt_names.join(', ')} To continue, this CSR needs to be cleaned." 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] 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.settings[: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 # @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 def certificate_is_alive?(cert) 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}" end store = 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}" 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-3.8.5/lib/puppet/ssl/certificate_authority/0000775005276200011600000000000012650174565022227 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/ssl/certificate_authority/autosign_command.rb0000664005276200011600000000230012650174557026077 0ustar jenkinsjenkinsrequire '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-3.8.5/lib/puppet/ssl/certificate_authority/interface.rb0000664005276200011600000001331112650174557024514 0ustar jenkinsjenkinsmodule 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] SUBJECTLESS_METHODS = [:list, :reinventory] 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}" 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| begin ca.verify(host) unless requests.include?(host) rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError => details verify_error = details.to_s end if verify_error certs[:invalid][host] = [ Puppet::SSL::Certificate.indirection.find(host), verify_error ] elsif (signed and signed.include?(host)) certs[:signed][host] = Puppet::SSL::Certificate.indirection.find(host) else certs[:request][host] = Puppet::SSL::CertificateRequest.indirection.find(host) end 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(ca, host, type, info, name_width) end end.flatten.compact.sort.join("\n") puts output end def format_host(ca, host, type, info, width) cert, verify_error = info alt_names = case type when :signed cert.subject_alt_names when :request cert.subject_alt_names else [] end alt_names.delete(host) alt_str = "(alt names: #{alt_names.map(&:inspect).join(', ')})" unless alt_names.empty? glyph = {:signed => '+', :request => ' ', :invalid => '-'}[type] name = host.inspect.ljust(width) fingerprint = cert.digest(@digest).to_s explanation = "(#{verify_error})" if verify_error [glyph, name, fingerprint, alt_str, explanation].compact.join(' ') 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 Puppet.err "Could not find certificate for #{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 Puppet.err "Could not find certificate for #{host}" end end end # Signs given certificates or waiting of subjects == :all def sign(ca) list = subjects == :all ? ca.waiting? : subjects raise InterfaceError, "No waiting certificate requests to sign" if list.empty? list.each do |host| ca.sign(host, options[:allow_dns_alt_names]) 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}" end @subjects = (value == []) ? nil : value end end end end end puppet-3.8.5/lib/puppet/ssl/certificate_factory.rb0000664005276200011600000002135412650174557022201 0ustar jenkinsjenkinsrequire 'puppet/ssl' # This class encapsulates the logic of creating and adding extensions to X509 # certificates. # # @api private module Puppet::SSL::CertificateFactory # Create, add extensions to, and sign a new X509 certificate. # # @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 [OpenSSL::X509::Request] 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.to_s} is an invalid certificate type!" raise ArgumentError, "Certificate TTL must be an integer" unless ttl.nil? || ttl.is_a?(Fixnum) # 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 private # 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 http://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>] 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 http://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 # 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. # # http://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] 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, val, crit) end end end puppet-3.8.5/lib/puppet/ssl/certificate_request.rb0000664005276200011600000002563712650174557022232 0ustar jenkinsjenkinsrequire '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 http://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 => <>] :csr_attributes A hash # of OIDs and values that are either a string or array of strings. # @option options [Array] :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}" # 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" unless csr.verify(key.public_key) @content = csr Puppet.info "Certificate Request fingerprint (#{digest.name}): #{digest.to_hex}" @content 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 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 context = "#{attribute.oid} extension index #{index}" # 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 ev = OpenSSL::X509::Extension.new(ext_values[0].value, ext_values[1].value) { "oid" => ev.oid, "value" => ev.value } when 3 ev = OpenSSL::X509::Extension.new(ext_values[0].value, ext_values[2].value, ext_values[1].value) { "oid" => ev.oid, "value" => ev.value, "critical" => ev.critical? } else raise Puppet::Error, "In #{attribute.oid}, expected extension record #{index} to have two or three items, but found #{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 http://tools.ietf.org/html/rfc2986 "RFC 2986 Certification Request Syntax Specification" # # @api public # # @return [Hash] 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.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" 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}: #{e.message}", e.backtrace end end end private 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" end ext = OpenSSL::X509::Extension.new(oid, value.to_s, false) extensions << ext rescue OpenSSL::X509::ExtensionError => e raise Puppet::Error, "Cannot create CSR with extension request #{oid}: #{e.message}", e.backtrace end end end if options[:dns_alt_names] names = options[:dns_alt_names].split(/\s*,\s*/).map(&:strip) + [name] names = names.sort.uniq.map {|name| "DNS:#{name}" }.join(", ") alt_names_ext = extension_factory.create_extension("subjectAltName", 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 http://tools.ietf.org/html/rfc2985#ref-10 5.4.2 CSR Extension Request structure # @see http://tools.ietf.org/html/rfc5280 4.1 Certificate Extension structure # # @api private # # @param attribute [OpenSSL::X509::Attribute] The X509 extension request # # @return [Array>] 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 #{attribute.oid}, expected Set but found #{attribute.value.class}" end unless attribute.value.value.is_a? Array raise Puppet::Error, "In #{attribute.oid}, expected Set[Array] but found #{attribute.value.value.class}" end unless attribute.value.value.size == 1 raise Puppet::Error, "In #{attribute.oid}, expected Set[Array] with one value but found #{attribute.value.value.size} elements" end unless attribute.value.value.first.is_a? OpenSSL::ASN1::Sequence raise Puppet::Error, "In #{attribute.oid}, expected Set[Array[Sequence[...]]], but found #{extension.class}" end unless attribute.value.value.first.value.is_a? Array raise Puppet::Error, "In #{attribute.oid}, expected Set[Array[Sequence[Array[...]]]], but found #{extension.value.class}" end extensions = attribute.value.value.first.value extensions.map(&:value) end end puppet-3.8.5/lib/puppet/ssl/certificate_request_attributes.rb0000664005276200011600000000232112650174557024461 0ustar jenkinsjenkinsrequire '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}") 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 #{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 #{hash.keys.inspect} in #{@path.inspect}" end return true end return false end end puppet-3.8.5/lib/puppet/ssl/certificate_revocation_list.rb0000664005276200011600000000661512650174557023741 0ustar jenkinsjenkinsrequire 'puppet/ssl/base' require 'puppet/indirector' # 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 => <] def ca_auth_certificates @ca_auth_certificates ||= decode_cert_bundle(read_file(ca_auth_file)) end ## # Decode a string of concatenated certificates # # @return [Array] 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) File.read(path) end private :read_file end end end puppet-3.8.5/lib/puppet/ssl/digest.rb0000664005276200011600000000051512650174557017443 0ustar jenkinsjenkinsclass 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-3.8.5/lib/puppet/ssl/host.rb0000664005276200011600000002705712650174557017153 0ustar jenkinsjenkinsrequire '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 => < :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}" 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 `. 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 def self.from_pson(pson) Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.") self.from_data_hash(pson) 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: #{certificate.fingerprint}" unless key unless certificate.content.check_private_key(key.content) raise Puppet::Error, < 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 # pson[: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] = 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, :SHA256, :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 SystemExit,NoMemoryError raise rescue Exception => detail Puppet.log_exception(detail, "Could not request certificate: #{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: #{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 end require 'puppet/ssl/certificate_authority' puppet-3.8.5/lib/puppet/ssl/inventory.rb0000664005276200011600000000310012650174557020212 0ustar jenkinsjenkinsrequire '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) Puppet.settings.setting(:cert_inventory).open("a") 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" Puppet.settings.setting(:cert_inventory).open('w') do |f| Puppet::SSL::Certificate.indirection.search("*").each do |cert| f.print format(cert.content) end end end # Find the serial number for a given certificate. def serial(name) Puppet.deprecation_warning 'Inventory#serial is deprecated, use Inventory#serials instead.' return nil unless Puppet::FileSystem.exist?(@path) serials(name).first 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) File.readlines(@path).collect do |line| /^(\S+).+\/CN=#{name}$/.match(line) end.compact.map { |m| Integer(m[1]) } end end puppet-3.8.5/lib/puppet/ssl/key.rb0000664005276200011600000000265312650174557016761 0ustar jenkinsjenkinsrequire '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 => < 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-3.8.5/lib/puppet/ssl/validator.rb0000664005276200011600000000310212650174557020144 0ustar jenkinsjenkinsrequire '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] peer certificates # # @api public # def peer_certs raise NotImplementedError, "Concrete class should have implemented this method" end # Contains the result of validation # @return [Array, 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-3.8.5/lib/puppet/ssl/validator/0000775005276200011600000000000012650174565017622 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/ssl/validator/default_validator.rb0000664005276200011600000001375312650174557023652 0ustar jenkinsjenkinsrequire '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] (Puppet::SSL::Host.localhost) the SSL host to use # # @api private # def initialize( ssl_configuration = Puppet::SSL::Configuration.new( Puppet[:localcacert], { :ca_chain_file => Puppet[:ssl_client_ca_chain], :ca_auth_file => Puppet[:ssl_client_ca_auth] }), ssl_host = Puppet::SSL::Host.localhost) 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](http://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) # We must make a copy since the scope of the store_context will be lost # across invocations of this method. if preverify_ok current_cert = store_context.current_cert @peer_certs << Puppet::SSL::Certificate.from_instance(current_cert) # 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 current_cert = store_context.current_cert @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-3.8.5/lib/puppet/ssl/validator/no_validator.rb0000664005276200011600000000050112650174557022625 0ustar jenkinsjenkinsrequire '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-3.8.5/lib/puppet/status.rb0000664005276200011600000000135112650174557016705 0ustar jenkinsjenkinsrequire '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 self.from_pson(pson) Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.") self.from_data_hash(pson) end def name "status" end def name=(name) # NOOP end def version @status['version'] end def version=(version) @status['version'] = version end end puppet-3.8.5/lib/puppet/test/0000775005276200011600000000000012650174565016013 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/test/test_helper.rb0000664005276200011600000002412112650174557020657 0ustar jenkinsjenkinsrequire 'puppet/indirector/data_binding/hiera' 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 us. # 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 owner = Process.pid Puppet.push_context(Puppet.base_context({ :environmentpath => "", :basemodulepath => "", :manifest => "/dev/null" }), "Initial for specs") Puppet::Parser::Functions.reset 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 invididual 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. $old_env = ENV.to_hash # 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::Node::Environment.clear Puppet::Application.clear! Puppet::Util::Profiler.clear Puppet.clear_deprecation_warnings Puppet::DataBinding::Hiera.instance_variable_set("@hiera", nil) 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 # 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 ENV.to_hash == $old_env ENV.clear $old_env.each {|k, v| ENV[k] = v } end # Some tests can cause us to connect, in which case the lingering # connection is a resource that can cause unexpected failure in later # tests, as well as sharing state accidentally. # We're testing if ActiveRecord::Base is defined because some test cases # may stub Puppet.features.rails? which is how we should normally # introspect for this functionality. ActiveRecord::Base.remove_connection if defined?(ActiveRecord::Base) # 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", :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) # Avoid opening ports to the outside world Puppet.settings[:bindaddress] = "127.0.0.1" # 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[:req_bits] = 512 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-3.8.5/lib/puppet/transaction.rb0000664005276200011600000002766312650174557017725 0ustar jenkinsjenkinsrequire 'puppet' require 'puppet/util/tagging' 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' 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 include Puppet::Util include Puppet::Util::Tagging def initialize(catalog, report, prioritizer) @catalog = catalog @report = report || Puppet::Transaction::Report.new("apply", 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] = {} } 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, relationship_graph, @prioritizer) @catalog.vertices.each { |resource| generator.generate_additional_resources(resource) } perform_pre_run_checks Puppet.info "Applying configuration 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 #{resource.provider.class.name} is not functional on this host" 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}" 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}") end end end relationship_graph.traverse(:while => continue_while, :pre_process => pre_process, :overly_deferred_resource_handler => overly_deferred_resource_handler, :canceled_resource_handler => canceled_resource_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] and @catalog.host_config? seconds = thinmark { block.call(resource) } resource.info "Evaluated in %0.2f seconds" % seconds if Puppet[:evaltrace] and @catalog.host_config? end end 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? } 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 prefetch_if_necessary(resource) provider_class = resource.provider.class return unless provider_class.respond_to?(:prefetch) and !prefetched_providers[resource.type][provider_class.name] 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) event_manager.queue_events(ancestor || resource, status.events) unless status.failed? rescue => detail resource.err "Could not evaluate: #{detail}" end # Evaluate a single resource. def eval_resource(resource, ancestor = nil) if skip?(resource) resource_status(resource).skipped = true else resource_status(resource).scheduled = true apply(resource, ancestor) end # Check to see if there are any events queued for this resource event_manager.process_events(resource) end def failed?(resource) s = resource_status(resource) and s.failed? end # Does this resource have any failed dependencies? def failed_dependencies?(resource) # First make sure there are no failed dependencies. To do this, # we check for failures in any of the vertexes above us. It's not # enough to check the immediate dependencies, which is why we use # a tree from the reversed graph. found_failed = false # 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)) relationship_graph.dependencies(resource).each do |dep| next unless failed?(dep) found_failed = true # See above. --daniel 2011-06-06 unless suppress_report then resource.notice "Dependency #{dep} has failures: #{resource_status(dep).failed}" end end found_failed end # A general method for recursively generating new resources from a # resource. def generate_additional_resources(resource) return unless resource.respond_to?(:generate) begin made = resource.generate rescue => detail resource.log_exception(detail, "Failed to generate additional resources using 'generate': #{detail}") end return unless made made = [made] unless made.is_a?(Array) made.uniq.each do |res| begin res.tag(*resource.tags) @catalog.add_resource(res) res.finish add_conditional_directed_dependency(resource, res) generate_additional_resources(res) rescue Puppet::Resource::Catalog::DuplicateResourceError res.info "Duplicate generated resource; skipping" end end 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 { |h, k| h[k] = {} } } @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] Puppet.debug "Prefetching #{provider_class.name} resources for #{type_name}" begin provider_class.prefetch(resources) rescue => detail Puppet.log_exception(detail, "Could not prefetch #{type_name} provider '#{provider_class.name}': #{detail}") 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 or resource_harness.scheduled?(resource) end # Should this resource be skipped? def skip?(resource) if 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.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 def split_qualified_tags? false 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 end require 'puppet/transaction/report' puppet-3.8.5/lib/puppet/transaction/0000775005276200011600000000000012650174565017361 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/transaction/additional_resource_generator.rb0000664005276200011600000001074612650174557026004 0ustar jenkinsjenkins# 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 def initialize(catalog, relationship_graph, prioritizer) @catalog = catalog @relationship_graph = relationship_graph @prioritizer = prioritizer end def generate_additional_resources(resource) return unless resource.respond_to?(:generate) begin generated = resource.generate rescue => detail resource.log_exception(detail, "Failed to generate additional resources using 'generate': #{detail}") end return unless generated generated = [generated] unless generated.is_a?(Array) generated.collect do |res| @catalog.resource(res.ref) || res end.each do |res| priority = @prioritizer.generate_priority_contained_in(resource, res) add_resource(res, resource, priority) add_conditional_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 resource.log_exception(detail, "Failed to generate additional resources using 'eval_generate': #{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) 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) if @catalog.resource(res.ref).nil? res.tag(*parent_resource.tags) @catalog.add_resource(res) @relationship_graph.add_vertex(res, priority) @catalog.add_edge(@catalog.container_of(parent_resource), res) res.finish 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-3.8.5/lib/puppet/transaction/event.rb0000664005276200011600000000537312650174557021040 0ustar jenkinsjenkinsrequire '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] YAML_ATTRIBUTES = %w{@audited @property @previous_value @desired_value @historical_value @message @name @status @time}.map(&:to_sym) 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 self.from_pson(data) Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.") self.from_data_hash(data) end def initialize(options = {}) @audited = false set_options(options) @time = Time.now end def initialize_from_hash(data) @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 end def to_data_hash { 'audited' => @audited, 'property' => @property, 'previous_value' => @previous_value, 'desired_value' => @desired_value, 'historical_value' => @historical_value, 'message' => @message, 'name' => @name, 'status' => @status, 'time' => @time.iso8601(9), } end def property=(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 #{EVENT_STATUSES.join(', ')}" unless EVENT_STATUSES.include?(value) @status = value end def to_s message end def to_yaml_properties YAML_ATTRIBUTES & super 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-3.8.5/lib/puppet/transaction/event_manager.rb0000664005276200011600000001002612650174557022521 0ustar jenkinsjenkinsrequire 'puppet/transaction' class Puppet::Transaction::EventManager attr_reader :transaction, :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 # Queue events for other resources to respond to. All of these events have # to be from the same resource. 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_events_for_resource(target, callback) target.info "Unscheduling #{callback} on #{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}" 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 def process_callback(resource, callback, events) process_noop_events(resource, callback, events) and return false unless events.detect { |e| e.status != "noop" } resource.send(callback) if not resource.is_a?(Puppet::Type.type(:whit)) resource.notice "Triggered '#{callback}' from #{events.length} events" end return true rescue => detail resource.err "Failed to call #{callback}: #{detail}" transaction.resource_status(resource).failed_to_restart = true resource.log_exception(detail) return false end def process_noop_events(resource, callback, events) resource.notice "Would have triggered '#{callback}' from #{events.length} events" # And then add an event for it. queue_events(resource, [resource.event(:status => "noop", :name => :noop_restart)]) true # so the 'and if' works end end puppet-3.8.5/lib/puppet/transaction/report.rb0000664005276200011600000002533512650174557021232 0ustar jenkinsjenkinsrequire '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 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 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 # 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] 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 'kind' of report is the name of operation that triggered the report to be produced. # Typically "apply". # @return [String] the kind of operation that triggered the generation of the report. # attr_reader :kind # 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 def self.from_data_hash(data) obj = self.allocate obj.initialize_from_hash(data) obj end def self.from_pson(data) Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.") self.from_data_hash(data) 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) @external_times[name] = value end # @api private def add_metric(name, hash) metric = Puppet::Util::Metric.new(name) hash.each do |name, value| metric.newvalue(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 (resource_metrics["failed"] || 0) > 0 'failed' elsif change_metric > 0 'changed' else 'unchanged' end 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 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) end # @api private def initialize(kind, configuration_version=nil, environment=nil, transaction_uuid=nil) @metrics = {} @logs = [] @resource_statuses = {} @external_times ||= {} @host = Puppet[:node_name_value] @time = Time.now @kind = kind @report_format = 4 @puppet_version = Puppet.version @configuration_version = configuration_version @transaction_uuid = transaction_uuid @environment = environment @status = 'failed' # assume failed until the report is finalized 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'] @host = data['host'] @time = data['time'] if @time.is_a? String @time = Time.parse(@time) end @kind = data['kind'] @metrics = {} data['metrics'].each do |name, hash| @metrics[name] = Puppet::Util::Metric.from_data_hash(hash) end @logs = data['logs'].map do |record| Puppet::Util::Log.from_data_hash(record) end @resource_statuses = {} data['resource_statuses'].map do |record| if record[1] == {} status = nil else status = Puppet::Resource::Status.from_data_hash(record[1]) end @resource_statuses[record[0]] = status end end def to_data_hash { 'host' => @host, 'time' => @time.iso8601(9), 'configuration_version' => @configuration_version, 'transaction_uuid' => @transaction_uuid, 'report_format' => @report_format, 'puppet_version' => @puppet_version, 'kind' => @kind, 'status' => @status, 'environment' => @environment, 'logs' => @logs, 'metrics' => @metrics, 'resource_statuses' => @resource_statuses, } 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 |name, label, value| report[key][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 status |= 2 if @metrics["changes"]["total"] > 0 status |= 4 if @metrics["resources"]["failed"] > 0 status |= 4 if @metrics["resources"]["failed_to_restart"] > 0 status end # @api private # def to_yaml_properties super - [:@external_times] end def self.supported_formats [:pson, :yaml] end def self.default_format Puppet[:report_serialization_format].intern end private 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| type = Puppet::Resource.new(name).type metrics[type.to_s.downcase] += status.evaluation_time if status.evaluation_time end @external_times.each do |name, value| metrics[name.to_s.downcase] = value end metrics["total"] = metrics.values.inject(0) { |a,b| a+b } metrics end end puppet-3.8.5/lib/puppet/transaction/resource_harness.rb0000664005276200011600000002071412650174557023265 0ustar jenkinsjenkinsrequire '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 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}") 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) return [] if ! allow_changes?(resource) # 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) end def allow_changes?(resource) if resource.purging? and resource.deleting? and deps = relationship_graph.dependents(resource) \ and ! deps.empty? and deps.detect { |d| ! d.deleting? } deplabel = deps.collect { |r| r.ref }.join(",") plurality = deps.length > 1 ? "":"s" resource.warning "#{deplabel} still depend#{plurality} on me -- not purging" false else true 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) 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 = "change from #{param.is_to_s(current_value)} to #{param.should_to_s(param.should)} failed: #{detail}" 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 = "change from #{param.is_to_s(current_value)} to #{param.should_to_s(param.should)} failed: #{detail}" raise ensure if event context.record(event) event.send_log context.synced_params << param.name end end end def create_change_event(property, current_value, historical_value) event = property.event event.previous_value = current_value event.desired_value = property.should event.historical_value = historical_value event 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. This compare satisfies compatibility requirements for # Ruby 1.8.7, where to_r does not exist on the Time class. 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 def audit_event(event, property) event.audited = true event.status = "audit" if !are_audited_values_equal(event.historical_value, event.previous_value) event.message = "audit change: previously recorded value #{property.is_to_s(event.historical_value)} has been changed to #{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) " (previously recorded value was #{param.is_to_s(historical_value)})" else "" end end def noop(event, param, current_value, audit_message) event.message = "current_value #{param.is_to_s(current_value)}, should be #{param.should_to_s(param.should)} (noop)#{audit_message}" event.status = "noop" end def sync(event, param, current_value, audit_message) param.sync event.message = "#{param.change_to_s(current_value, param.should)}#{audit_message}" 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) event.send_log context.record(event) end else resource.property(param_name).notice "audit change: newly-recorded value #{context.current_values[param_name]}" end end end # @api private ResourceApplicationContext = Struct.new(:resource, :current_values, :historical_values, :audited_params, :synced_params, :status) 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) end def resource_present? resource.present?(current_values) end def record(event) status << event end end end puppet-3.8.5/lib/puppet/type.rb0000664005276200011600000027021312650174557016350 0ustar jenkinsjenkinsrequire '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 # or Ruby 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 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) # We only order against other types, not arbitrary objects. return nil unless other.is_a? Puppet::Type # Our natural order is based on the reference name we use when comparing # 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 # 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 value(s). # 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 #{options.inspect}" end raise Puppet::DevError, "Class #{self.name} already has a property named #{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 #{self} has not defined parameters" 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 resoure 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("Invalid parameter #{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}") 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 '#{attr}' in #{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 insance. # 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 upspecific 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 [Boolan] 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? # @overload remove(rmdeps) # @deprecated Use remove() # @param rmdeps [Boolean] intended to indicate that all subscriptions should also be removed, ignored. # @overload remove() # @return [void] # def remove(rmdeps = true) # 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 raise Puppet::DevError, "The is value is not in the is array for '#{property.name}'" end ensureis = is[property] if property.safe_insync?(ensureis) and property.should == :absent return true end end properties.each { |property| unless is.include? property raise Puppet::DevError, "The is value is not in the is array for '#{property.name}'" end propis = is[property] unless property.safe_insync?(propis) property.debug("Not in sync: #{propis.inspect} vs #{property.should.inspect}") insync = false #else # property.debug("In sync") end } #self.debug("#{self} sync status is #{insync}") insync end # Retrieves the current value of all contained properties. # Parameters and meta-parameters are not included in the result. # @todo As oposed 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.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 overridded this method. def self.instances raise Puppet::DevError, "#{self.name} has no providers and has not overridden 'instances'" if provider_hash.empty? # Put the default provider first, then the rest of the suitable providers. provider_instances = {} providers_by_source.collect do |provider| self.properties.find_all do |property| provider.supports_parameter?(property) end.collect do |property| property.name end 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) hash.each do |param, value| resource[param] = value end resource end # Returns an array of strings representing the containment heirarchy # (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](http://docs.puppetlabs.com/references/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](http://docs.puppetlabs.com/references/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 list = all_properties else list = 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: * `crit` * `emerg` * `alert` * `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 => 644, } 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](/references/latest/configuration.html#tags) (e.g. `puppet agent --test --tags bootstrap`) or filtering alerts with [the `tagmail` report processor](http://docs.puppetlabs.com/references/latest/report.html#tagmail)." 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 value(s) 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.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 futher 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 } self.debug("subscribes to #{related_resource.ref}") else # If there's no callback, there's no point in even adding # a label. subargs = nil self.debug("requires #{related_resource.ref}") end Puppet::Relationship.new(source, target, subargs) end end end # @todo document this, have no clue what this does... it retuns "RelationshipMetaparam.subclasses" # def self.relationship_params RelationshipMetaparam.subclasses end # Note that the order in which the relationships params is defined # matters. The labelled params (notify and subcribe) 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](http://docs.puppetlabs.com/puppet/latest/reference/lang_datatypes.html#resource-references). Multiple resources can be specified as an array of references. When this attribute is present: * The required resource(s) 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](http://docs.puppetlabs.com/puppet/latest/reference/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](http://docs.puppetlabs.com/puppet/latest/reference/lang_datatypes.html#resource-references). Multiple resources can be specified as an array of references. When this attribute is present: * The subscribed resource(s) 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](http://docs.puppetlabs.com/puppet/latest/reference/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](http://docs.puppetlabs.com/puppet/latest/reference/lang_datatypes.html#resource-references). Multiple resources can be specified as an array of references. When this attribute is present: * This resource will be applied _before_ the dependent resource(s). 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](http://docs.puppetlabs.com/puppet/latest/reference/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](http://docs.puppetlabs.com/puppet/latest/reference/lang_datatypes.html#resource-references). Multiple resources can be specified as an array of references. When this attribute is present: * This resource will be applied _before_ the notified resource(s). * 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](http://docs.puppetlabs.com/puppet/latest/reference/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](http://docs.puppetlabs.com/references/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 ############################### # 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 #{self.name}: #{defaults.collect { |i| i.name.to_s }.join(", ")}; using #{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 #{pname} of #{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 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.class.name} provider '#{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.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 #{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 autorequire. # 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'] }) # # @param name [String] the name of a type of which one or several resources should be autorequired 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 # Provides iteration over added auto-requirements (see {autorequire}). # @yieldparam type [String] the name of the type to autoriquire 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 # Adds dependencies to the catalog from added autorequirements. # 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 autorequire(rel_catalog = nil) rel_catalog ||= catalog raise(Puppet::DevError, "You cannot add relationships without a catalog") unless rel_catalog reqs = [] self.class.eachautorequire { |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| # Support them passing objects directly, to save some effort. unless dep.is_a? Puppet::Type # Skip autorequires that we aren't managing unless dep = rel_catalog.resource(type, dep) next end end reqs << Puppet::Relationship.new(dep, self) } } reqs 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 awsome 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 public # 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 captialized # 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 public # @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 @tags = resource.tags @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 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 #{attr} on #{self.class.name}: #{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 explicetly 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 explicetly 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.tag(*self.tags) @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 require 'puppet/provider' puppet-3.8.5/lib/puppet/type/0000775005276200011600000000000012650174565016015 5ustar jenkinsjenkinspuppet-3.8.5/lib/puppet/type/augeas.rb0000664005276200011600000001543112650174557017614 0ustar jenkinsjenkins# # Copyright 2011 Bryan Kearney # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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. Supported onlyif syntax: * `get ` * `match size ` * `match include ` * `match not_include ` * `match == ` * `match != ` 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 ` --- Sets the value `VALUE` at loction `PATH` * `setm ` --- Sets multiple nodes (matching `SUB` relative to `PATH`) to `VALUE` * `rm ` --- Removes the node at location `PATH` * `remove ` --- Synonym for `rm` * `clear ` --- Sets the node at `PATH` to `NULL`, creating it if needed * `clearm ` --- Sets multiple nodes (matching `SUB` relative to `PATH`) to `NULL` * `ins