cernops/packaging

Name: packaging

Owner: CERN Operations

Description: null

Created: 2015-05-12 08:22:59.0

Updated: 2015-05-12 08:22:59.0

Pushed: 2015-05-12 09:22:34.0

Homepage: null

Size: 1595

Language: Ruby

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

Packaging

Build Status

This is a repository for packaging automation for Puppet Labs software. The goal is to abstract and automate packaging processes beyond individual software projects to a level where this repo can be cloned inside any project and used to build Debian and Redhat packages, as well as gems, apple packages and tarballs. This repo is currently under heavy development and in a state flux, and it should not be considered to have a formal API. However, every effort is being made to ensure existing tasks/behavior are not broken as we continue to iterate and improve upon it.

Using the Packaging Repo

Several Puppet Labs projects are using the packaging repo. They are:

as well as several closed-source projects, including

Generally speaking, the packaging repo should be compatible with ruby 1.8.7, ruby 1.9.3 and rake 0.9.x. To pull the packaging tasks into your source repo, do a rake package:bootstrap. This will clone this repo into the ext directory of the project and make many packaging tasks available. The tasks are generally grouped into two categories, package: namespaced tasks and pl: namespaced tasks.

package: tasks

package: namespaced tasks are general purpose tasks that are set up to use the most minimal tool chain possible for creating packages. These tasks will create rpms and debs, but any build dependencies will need to be satisifed by the building host, and any dynamically generated dependencies may result in packages that are only suitable for the OS/version of the build host. However, for rolling one's own debs and rpms or for use in environments without many OSes/versions, this may work just fine. To build an rpm using the packaging repo, do a rake package:rpm. To build a deb, use rake package:deb.

pl: tasks

pl: namespaced tasks rely on a slighly more complex toolchain for packaging inside clean chroot environments for the various operating systems and versions that Puppet Labs supports. On the rpm side, this is done with mock and for debs, we use pdebuild and cowbuilder. For the most part, these tasks are keyed to puppetlabs infrastructure, and are used by the Release Engineering team to create release packages. However, they can certainly be modified to suit other environments, and much effort went into making tasks as modular and reusable as possible. Several Puppet Labs-specific tasks are only available if the file '~/.packaging' is present. This file is created by the pl:fetch task, which curls two yaml files into 'team' and 'project' subdirectories from a separate build data repository, which contains additional settings/data specific to Puppet Labs release infrastructure. By default, the team data file is pulled from the 'dev' branch of the repo, and the project data file is pulled from a branch named after the project (e.g. for puppet, there is a branch named puppet with a build data file). The goal in separating these data and tasks out is to refrain from presenting by default yet more Puppet Labs-specific tasks that aren't generally consumable by everyone. To build a deb from a local repository using a pl task, ssh into a builder (e.g., one stood up using the modules detailed below) and clone the source repo, e.g. puppet. Then, run rake package:bootstrap and rake pl:deb to create a deb, and rake pl:mock to make an rpm (on a debian or redhat host, respectively).

pe: tasks

There is also a pe: namespace, for the building of Puppet Labs' Puppet Enterprise packages that have been converted to using this repo. The pe: tasks rely heavily on PL internal infrastructure, and are not generally useful outside of this environment. To create packages, in the source repository run rake package:bootstrap, followed by rake pl:fetch. These two commands bootstrap the packaging environment and pull in the additional data needed for PE building (see pl:fetch notes above). Then, to make a debian package, run rake pe:deb, and to make an rpm, run rake pe:mock. There are also pe:deb_all and pe:mock_all tasks, which build packages against all shipped debian/redhat targets. The pe:deb_all task is not generally necessary for developer use for building test packages; the pe:deb task creates a package that will work against virtually all supported PE debian versions. The same is generally true for PE internal rpms, but because of variances in build macros for rpm, rpms should generally be built with pe:mock_all, and then the desired version installed, or by building only for a specific target. This is accomplished by passing MOCK= to the rake call, e.g. rake pe:mock MOCK=<mock>. The available mocks are listed in ext/build_defaults.yaml after final_mocks:. For PE, the mocks are formatted as pupent-<peversion>-<distversion>-<arch>, e.g. pupent-2.7-el5-i386. To build for a specific target, set MOCK=<mock> to the mock that matches the target.

:remote: tasks

There are also sub-namespaces of :pl and :pe that are worth noting. First, the :remote namespace. Tasks under :remote perform builds remotely on internal builders from your local workstation. How they work:

1) Run pl:fetch to obtain extra data from the build-data repo. The data includes the hostnames of builders to use for packaging.

2) Create a git bundle of the local workspace and tar it up.

3) Create a build parameters file. The params file includes all the information about the build, including any values overridden with env vars, and the actual task to run, e.g. rake pl:deb.

4) scp the git bundle and build parameters file to a temporary directory on the builder hostname assigned to that particular package build type.

5) ssh into the builder, untar the git bundle, clone it, and run rake package:bootstrap.

6) ssh into the builder, cd into the cloned repo, and run rake pl:build_from_params PARAMS_FILE=/path/to/previously/sent/file.

7) Maintain the ssh connection until the build finishes, and rsync the packages from the builder to the local workstation.

Note that these tasks require ssh access to the builder hosts that are specified in the build-data file, and appropriate membership in the build groups, e.g. to use mock on the builder, membership in the mock group. This is a major hurdle, and is resolved with the jenkins tasks below.

legacy :jenkins: workflow tasks

(Deprecated - see “dyamic jenkins task workflow” below) Jenkins tasks are similar to the :remote: tasks, but they do not require ssh access to the builders. They do require being on the local network - the jenkins instance that performs package builds is an internal server only, accessible when connected via VPN or on-site. The jenkins tasks enable the packaging repo to kick off packaging builds on a remote jenkins slave. There are two workflows of jenkins tasks in the packaging repo. The first workflow, which is used for creating individual platform packages on jenkins (e.g. creating a deb with pl:jenkins:deb) relies on a job that exists on a remote jenkins server. The tasks transmit information to the jenkins job, which handles the rest. The data passed are the following:

1) $PROJECT_BUNDLE - a tar.gz of a git-bundle from HEAD of the current project, which is cloned on the builder to set up a duplicate of this environment

2) $BUILD_PROPERTIES - a build parameters file, containing all information about the build

3) $BUILD_TYPE - the “type” of build, e.g. rpm, deb, gem, etc The jenkins url and job name are obtained via the team build-data file from the build data repository

4) $PROJECT - the project we're building, e.g. facter, puppet. This is used later in determining the target for the build artifacts on the distribution server

4) $METRICS - a string of data points related to the build which are used for data analysis. Contents of this string are items which cannot be obtained from within the Jenkins job itself.

5) $DOWNSTREAM_JOB - The URL of a downstream job that jenkins should post to upon success. This is obtained via the DOWNSTREAM_JOB environment variable.

On the Jenkins end, the job is a parameterized job that accepts five parameters. Jenkins has the Parameterized Trigger Plugin, Workspace Cleanup Plugin, and Node and Label Parameter Plugin in use for this job. The workspace cleanup plugin cleans the workspace before each build. Two are file parameters, two string parameters, and a Label parameter provided by the Node and Label Parameter Plugin, as described above. When the pl:jenkins:* task triggers a build, it passes values for all of these parameters. The Label parameter is associated with the build type. This way we can queue the job on a builder with the appropriate capabilities just by assigning a builder the label “deb” or “rpm,” etc. The job allows parallel execution of jobs - in this way, we can queue many package jobs on the jenkins instance, which will farm them out to builders that are slaves of that jenkins instance. This also allows us to scale building capacity simply by adding builders as slaves to the jenkins instance. The actual build itself is accomplished via a shell build task. The contents of the task are:

#############

A=$(echo $BUILD_PROPERTIES | cut -d '.' -f1)

ho "Build type: $BUILD_TYPE"

 Create a local clone of the git-bundle that was passed
he bundle is a tarball, and since this is a project-agnostic
ob, we don't actually know what's in it, just that it's a
it bundle.

-f "PROJECT_BUNDLE" ] || exit 1
dir project && tar -xzf PROJECT_BUNDLE -C project/

 project
git clone --recursive $(ls) git_repo

cd git_repo

  ### Clone the packaging repo
  rake package:bootstrap && rake pl:fetch

  ### Perform the build
  rake pl:load_extras pl:build_from_params PARAMS_FILE=$WORKSPACE/BUILD_PROPERTIES

  ### Send the results
  rake pl:jenkins:ship["artifacts"]

  ### If a downstream job was passed, trigger it now
  if [ -n "$DOWNSTREAM_JOB" ] ; then
    rake pl:jenkins:post["$DOWNSTREAM_JOB"]
  fi

#############

To gather metrics related to a Jenkins build, the Groovy Postbuild plugin is used. For tasks carried out on the static Jenkins job, the script must be manually added to the job's configuration. The script in its entirety can be seen here.

dynamic :jenkins: task workflow

The recommended and far simpler jenkins-based workflow is for initiating the “uber_build”, or a package build for all of our target platforms.

The uber_build is invoked as “pl:jenkins:uber_build” or “pe:jenkins:uber_build” depending on if this is a FOSS or PE package.

This workflow doesn't actually use a static job on the jenkins-server. Instead it creates the jenkins jobs for you, on-demand. Specifically, it creates two jenkins-jobs, and can create an optional third.

The first job is a matrix job, the cells of which are individual package tasks for all of the build targets. This job takes four parameters:

1) $PROJECT_BUNDLE - a tar.gz of a git-bundle from HEAD of the current project, which is cloned on the builder to set up a duplicate of this environment

2) $BUILD_PROPERTIES - a build parameters file, containing all information about the build

3) $PROJECT - the project we're building, e.g. facter, puppet. This is used later in determining the target for the build artifacts on the distribution server

4) $METRICS - a string of data points related to the build which are used for data analysis. Contents of this string are items which cannot be obtained from within the Jenkins job itself. Note that the Groovy postbuild script needed for metrics gathering is dynamically passed to each job.

This first job clones the git bundle passed in as a parameter, then clones the packaging repo (rake package:bootstrap) and for every cell in its matrix performs a package build for a specific target (e.g. rake pl:deb COW=base-lucid-i386.cow). Once all cells in the matrix complete (either succeed or fail), this job automatically triggers the second of the new jobs as a downstream job.

To receive an email notification from jenkins about the status of the packaging job, pass NOTIFY= as an environment variable to the uber_build invocation, e.g.:

rake pl:jenkins:uber_build NOTIFY=“foo@puppetlabs.com bar@puppetlabs.com”

The second job is an automatic repository creation task for this git repo. Specifically, the job copies the git bundle from the packaging job and clones it, and uses the git information in the git bundle to clone the packaging repo and invoke the repository creation jobs pl:jenkins:rpm_repos and pl:jenkins:deb_repos. The job will always be invoked, but will only actually create repos if the upstream packaging job actually succeeded.

The third job is only created if the environment variable DOWNSTREAM_JOB=<job_url> was passed to the initial “pl:jenkins:uber_build” invocation. This third job takes the value assigned to DOWNSTREAM_JOB and creates a proxy jenkins job with a single build step, a curl call to this value, presumably a url to a jenkins job to trigger programmatically.

An important note about DOWNSTREAM_JOB: DOWNSTREAM_JOB in the dynamic jenkins workflow is always invoked if it is passed in as an environment variable. However, it is appended with an additional parameter, PACKAGE_BUILD_STATUS, which will be the string “success” if package and repo builds succeeded, or “failure” if package or repo builds failed. By modifying the actual downstream jenkins job to accept a string parameter of PACKAGE_BUILD_STATUS, one can switch on the success or failure of the packaging job, responding appropriately. A second parameter, PACKAGE_BUILD_URL is also appended to DOWNSTREAM_JOB, the value of which is the url of the packaging job itself. This is to assist with tracing failures in a multi-jenkins environment. By modifying the downstream jenkins job to accept a string parameter of PACKAGE_BUILD_URL, one can use the value to display the url prominently in case of failure, for example.

E.g., a job url: http://jenkins.example.net/job/downstream/buildWithParameters?FOO=bar

in the success case will be transformed into

http://jenkins.example.net/job/downstream/buildWithParameters?FOO=bar&PACKAGE_BUILD_STATUS=success&PACKAGE_BUILD_URL=http://jenkins.example.net/job/packaging_job

and in the failure case transformed into

http://jenkins.example.net/job/downstream/buildWithParameters?FOO=bar&PACKAGE_BUILD_STATUS=failure&PACKAGE_BUILD_URL=http://jenkins.example.net/job/packaging_job

Since jenkins will just drop parameters that are not configured in the job, accepting PACKAGE_BUILD_STATUS and PACKAGE_BUILD_URL in the downstream job isn't mandatory.

All 3 jobs are configured by default for removal by jenkins after 3 days, to avoid clutter.

The goal is to move toward migrating all of the jenkins tasks from the first workflow, using a static job that is called many times per package, to this second workflow of creating the jobs on demand.

Polling behavior with pl:jenkins:uber_build:

It is possible to pass an integer argument to the uber_build task which will be used by the job as polling interval. If this value is passed in, the task will query the dynamic job using the Jenkins API periodically until the build is finished. Then, it will query the build to determine the SUCCESS/FAILURE status. The job output will look something like:

aging SUCCESS
 SUCCESS

If for example the Packaging job had failed the output would look like this:

aging FAILURE

If a build fails then the rake task will terminate with a nonzero exit status which can be used during CI or other automated contexts to detect and act on the failure. This eliminates the need to pass a DOWNSTREAM_JOB variable to the uber_build job although it is still possible to do so.

Task Explanations

For a listing of all available tasks and their functions, see the Task Dictionary at the end of this README.

Modules

A puppet module, puppetlabs-debbuilder, has been created to stand up a debian build host compatible with the debian side of this packaging repo. The rpm-side module, puppetlabs-rpmbuilder, will set up an rpm builder.

Clean up

To remove the packaging repo, remove the ext/packaging directory or run rake package:implode.

Setting up projects for the Packaging Repo

The packaging repo requires many project-side artifacts inside the ext directory at the top level. facter and hiera are good examples. It expects the following directory structure in the project

each of which contains templated erb files using the instance variables specified in the setupvars task. These include a debian changelog, a redhat spec file, and an osx preflight and plist.

The top level Rakefile or a separate task file in the project should have the following added:

(this assumes RAKE_ROOT is defined in the top-level Rakefile using something like the following)

_ROOT = File.expand_path(File.dirname(__FILE__))
uby
d_defs_file = File.join(RAKE_ROOT, 'ext', 'build_defaults.yaml')
ile.exist?(build_defs_file)
gin
require 'yaml'
@build_defaults ||= YAML.load_file(build_defs_file)
scue Exception => e
STDERR.puts "Unable to load yaml from #{build_defs_file}:"
raise e
d
ackaging_url  = @build_defaults['packaging_url']
ackaging_repo = @build_defaults['packaging_repo']
ise "Could not find packaging url in #{build_defs_file}" if @packaging_url.nil?
ise "Could not find packaging repo in #{build_defs_file}" if @packaging_repo.nil?

mespace :package do
desc "Bootstrap packaging automation, e.g. clone into packaging repo"
task :bootstrap do
  if File.exist?(File.join(RAKE_ROOT, "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 File.join(RAKE_ROOT, 'ext') do
      %x{git clone #{@packaging_url}}
    end
  end
end
desc "Remove all cloned packaging automation"
task :implode do
  rm_rf File.join(RAKE_ROOT, "ext", @packaging_repo)
end
d


n
ad File.join(RAKE_ROOT, 'ext', 'packaging', 'packaging.rake')
ue LoadError

Also in ext should be two files, build_defaults.yaml and project_data.yaml.

This is the sample build_defaults.yaml file from Hiera:


aging_url: 'git@github.com:puppetlabs/packaging --branch=master'
aging_repo: 'packaging'
ult_cow: 'base-squeeze-i386.cow'
ich debian distributions to build for. Noarch packages only need one arch of each cow.
: 'base-lucid-amd64.cow base-lucid-i386.cow base-natty-amd64.cow base-natty-i386.cow base-oneiric-amd64.cow base-oneiric-i386.cow base-precise-amd64.cow base-precise-i386.cow base-sid-amd64.cow base-sid-i386.cow base-squeeze-amd64.cow base-squeeze-i386.cow base-testing-amd64.cow base-testing-i386.cow base-wheezy-i386.cow'
e pbuilder configuration file to use
ld_conf: '/etc/pbuilderrc'
ternate debian mirrors to build against (must be an array)
e __DIST__ string is automatically replaced with codename of the cow being built, so when the squeeze cow is being built the deb_build_mirrors will be:
b http://apt.puppetlabs.com squeeze main dependencies
b http://somethingelse.com/debian squeeze
is will happen for each cow during the build.
build_mirrors:
deb http://apt.puppetlabs.com __DIST__ main dependencies
deb http://somethingelse.com/debian __DIST__
o is packaging. Turns up in various packaging artifacts
ager: 'puppetlabs'
G key ID of the signer
key: '4BD6EC30'
ether to require tarball signing as a prerequisite of other package building
_tar: FALSE
space separated list of mock configs. These are the rpm distributions to package for. If a noarch package, only one arch of each is needed.
l_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-fedora-16-i386 pl-fedora-16-x86_64 pl-fedora-17-i386 pl-fedora-17-x86_64'
e host that contains the yum repository to ship to
host: 'yum.puppetlabs.com'
e remote path the repository on the yum\_host
repo_path: '/some/repo/'
e host that contains the apt repository to ship to
host: 'apt.puppetlabs.com'
e URL to use for the apt dependencies in cow building
repo_url: 'http://apt.puppetlabs.com'
e path on the remote apt host that debs should ship to
repo_path: '/opt/repository/incoming'
e host that stores the tarballs for downloading
host: 'downloads.puppetlabs.com'
ether to present the gem and apple tasks
d_gem: TRUE
d_dmg: TRUE
ether to execute the rdoc rake tasks prior to composing the tarball
d_doc: FALSE
ether to kick of a dynamic msi build job along side the uber_build
 present, a dynamically generated jenkins job will be kicked off.
e automation in puppet_for_the_win is used to build the msi with the
llowing components.
d_msi:
ppet_for_the_win:
ref: 'origin/master'
repo: 'git://github.com/puppetlabs/puppet_for_the_win.git'
cter:
ref: 'refs/tags/2.1.0'
repo: 'git://github.com/puppetlabs/facter.git'
era:
ref: 'refs/tags/1.3.4'
repo: 'git://github.com/puppetlabs/hiera.git'
s:
ref:
  x86: 'origin/1.9.3-x86'
  x64: 'origin/2.0.0-x64'
repo: 'git://github.com/puppetlabs/puppet-win32-ruby.git'
ether to present the Solaris 11 IPS packaging tasks
is requires suitable IPS packaging artifacts in the project in ext/ips
d_ips: FALSE
ether this project is a PE project or not
d_pe: FALSE
 optional task to execute pre-tarball composition. See the tasks in
e 'pretasks' directory
tar_task: 'package:vendor_gems'

This is the sample project_data.yaml file:


ect: 'hiera'
or: 'Puppet Labs'
l: 'info@puppetlabs.com'
page: 'https://github.com/puppetlabs/hiera'
ary: 'Light weight hierarchical data store'
ription: 'A pluggable data store for hierarcical data'
le containing hard coded version information, if present
ion_file: '/lib/hiera.rb'
string indicating the version strategy for the project (one of 'odd_even' or 'rc_final'), defaults to rc_final
d_even is a final release when the minor version is even, and a development release when it is odd
_final is a final release when there is no rc at the end of the version string, and a development release when there is
ion_strategy: 'rc_final'
olean value of whether or not to automatically update the version file before packaging (defaults to false)
te_version_file: true
les and gem\_files are space separated lists
les to be packaged into a tarball and released with deb/rpm
s: '[A-Z]* ext lib bin spec acceptance_tests'
ace separated list of files to *exclude* from the tarball
te that each listing in files, above, is recursively copied into the tarball, so
ar\_excludes' only needs to include any undesired subdirectories/files of the 'files'
st to exclude
excludes: 'ext/packaging lib/some_excluded_file'
ray of templates or globs of templates to evaluate. Note that without this key, the packaging will
fault to searching for any files in `ext` with the extension '.erb' and evaluate them. When this
y is supplied, its values override the defaults, and all desired erb files must be specified with a path or glob.
lates:
ext/redhat/project.spec.erb
ext/templates/**/*.erb
les to be packaged into a gem
files: '{bin,lib}/**/* CHANGELOG COPYING README.md LICENSE'
 exclude specific files from inclusion in a gem:
excludes: 'lib/hiera/file_to_exclude.rb lib/hiera/directory_to_exclude'
 gem name differs in some way from project name, e.g. only build gem for a client end
name: hiera_the_gem
 gem summary and/or description differs from general summary
summary: 'A sub-set of the Hiera pluggable data store'
description: 'A gem of the pluggable data store for hierarchical data'
require_path: 'lib'
test_files: 'spec/**/*'
executables: 'hiera'
default_executables: 'hiera'
 add gem dependencies, indent.
is is an example only, hiera doesn't really depend on hiera-puppet/json/facter
r no specific version, leave version empty
runtime_dependencies:
era-puppet: '1.0.0rc'
era-json:
development_dependencies:
cter: '>= 1.6.11'
 add gem dependencies which only apply to a specific platform, add the key "gem_platform_dependencies".
e first key under the gem_platform_dependencies has to be a value that
rresponds to a value of RUBY_PLATFORM. The subkeys are the same as the
p-level gem dependency keys:
platform_dependencies:
6-mingw32:
gem_runtime_dependencies:
  win32process: '~> 0.6.5'
gem_development_dependencies:
  rake: '~> 0.9.0'
6_64-darwin:
gem_runtime_dependencies:
  CFPropertyList: '~> 2.2.4'
oc options as an array
rdoc_options:
--line-numbers
--main
Hiera.README

For basic mac packaging, add an osx directory in ext containing the following files:

  1. a preflight.erb template for any pre-flight actions, perhaps removing the old package if present.
  2. a prototype.plist.erb that is templated into the pkginfo.plist file for the package. This is the one from puppet. Note that these variable names aren't mutable here, but there's no need to worry about their value assignment, it's done in the apple task:
    l version="1.0" encoding="UTF-8"?>
    CTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    st version="1.0">
    t>
    >CFBundleIdentifier</key>
    ing><%= @title %></string>
    >CFBundleShortVersionString</key>
    ing><%= @version %></string>
    >IFMajorVersion</key>
    eger><%= @package_major_version %></integer>
    >IFMinorVersion</key>
    eger><%= @package_minor_version %></integer>
    >IFPkgBuildDate</key>
    e><%= @build_date %></date>
    >IFPkgFlagAllowBackRev</key>
    se/>
    >IFPkgFlagAuthorizationAction</key>
    ing>RootAuthorization</string>
    >IFPkgFlagDefaultLocation</key>
    ing>/</string>
    >IFPkgFlagFollowLinks</key>
    e/>
    >IFPkgFlagInstallFat</key>
    se/>
    >IFPkgFlagIsRequired</key>
    se/>
    >IFPkgFlagOverwritePermissions</key>
    se/>
    >IFPkgFlagRelocatable</key>
    se/>
    >IFPkgFlagRestartAction</key>
    ing><%= @pm_restart %></string>
    >IFPkgFlagRootVolumeOnly</key>
    e/>
    >IFPkgFlagUpdateInstalledLanguages</key>
    se/>
    ct>
    ist>
    
    A file_mapping.yaml file that specifies a set of files and a set of directories from the source to install, with destinations, ownership, and permissions. The directories are top level directories in the source to install. The files are files somewhere in the source to install. This is the one from puppet 3.x:
    
    ctories:
    is will take the contents of lib, e.g. puppet/lib/\* and place them in
    sr/lib/ruby/site\_ruby/1.8
    
    : 'usr/lib/ruby/site_ruby/1.8'
    r: 'root'
    p: 'wheel'
    s: '0644'
    
    : 'usr/bin'
    r: 'root'
    p: 'wheel'
    s: '0755'
    /man8':
    : 'usr/share/man/man8'
    r: 'root'
    p: 'wheel'
    s: '0755'
    s:
    is will take the file puppet/conf/auth.conf and place it in
    rivate/etc/puppet/, creating the directory if not present
    f/auth.conf':
    : 'private/etc/puppet'
    r: 'root'
    p: 'wheel'
    s: '0644'
    /man5/puppet.conf.5':
    : 'usr/share/man/man5'
    r: 'root'
    p: 'wheel'
    s: '0644'
    Z]*':
    : 'usr/share/doc/puppet'
    r: 'root'
    p: 'wheel'
    s: '0644'
    
Task Dictionary

This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.