diff --git a/README.markdown b/README.markdown
index a8d0f65..e7f2d77 100644
--- a/README.markdown
+++ b/README.markdown
@@ -1,883 +1,883 @@
# vcsrepo
#### 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 vcsrepo](#setup)
* [Setup requirements](#setup-requirements)
* [Beginning with vcsrepo](#beginning-with-vcsrepo)
4. [Usage - Configuration options and additional functionality](#usage)
* [Git](#git)
* [Bazaar](#bazaar)
* [CVS](#cvs)
* [Mercurial](#mercurial)
* [Perforce](#perforce)
* [Subversion](#subversion)
5. [Reference - An under-the-hood peek at what the module is doing and how](#reference)
* [Type: vcsrepo](#type-vcsrepo)
* [Providers](#providers)
* [Features](#features)
* [Parameters](#parameters)
5. [Limitations - OS compatibility, etc.](#limitations)
6. [Development - Guide for contributing to the module](#development)
## Overview
The vcsrepo module lets you use Puppet to easily deploy content from your version control system (VCS).
## Module description
The vcsrepo module provides a single type with providers to support the following version control systems:
* [Git](#git)
* [Bazaar](#bazaar)
* [CVS](#cvs)
* [Mercurial](#mercurial)
* [Perforce](#perforce)
* [Subversion](#subversion)
**Note:** `git` is the only vcs provider officially [supported by Puppet Inc.](https://forge.puppet.com/supported)
## Setup
### Setup requirements
The vcsrepo module does not install any VCS software for you. You must install a VCS before you can use this module.
Like Puppet in general, the vcsrepo module does not automatically create parent directories for the files it manages. Set up any needed directory structures before you start.
### Beginning with vcsrepo
To create and manage a blank repository, define the type `vcsrepo` with a path to your repository and supply the `provider` parameter based on the [VCS you're using](#usage).
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => git,
}
~~~
## Usage
**Note:** `git` is the only vcsrepo provider officially [supported by Puppet Inc.](https://forge.puppet.com/supported)
### Git
#### Create a blank repository
To create a blank repository suitable for use as a central repository, define `vcsrepo` without `source` or `revision`:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => git,
}
~~~
If you're managing a central or official repository, you might want to make it a bare repository. To do this, set `ensure` to 'bare':
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => bare,
provider => git,
}
~~~
#### Clone/pull a repository
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => git,
source => 'git://example.com/repo.git',
}
~~~
To clone your repository as bare or mirror, you can set `ensure` to 'bare' or 'mirror':
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => mirror,
provider => git,
source => 'git://example.com/repo.git',
}
~~~
By default, `vcsrepo` will use the HEAD of the source repository's master branch. To use another branch or a specific commit, set `revision` to either a branch name or a commit SHA or tag.
Branch name:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => git,
source => 'git://example.com/repo.git',
revision => 'development',
}
~~~
SHA:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => git,
source => 'git://example.com/repo.git',
revision => '0c466b8a5a45f6cd7de82c08df2fb4ce1e920a31',
}
~~~
Tag:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => git,
source => 'git://example.com/repo.git',
revision => '1.1.2rc1',
}
~~~
To check out a branch as a specific user, supply the `user` parameter:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => git,
source => 'git://example.com/repo.git',
revision => '0c466b8a5a45f6cd7de82c08df2fb4ce1e920a31',
user => 'someUser',
}
~~~
To keep the repository at the latest revision, set `ensure` to 'latest'.
**WARNING:** This overwrites any local changes to the repository.
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => latest,
provider => git,
source => 'git://example.com/repo.git',
revision => 'master',
}
~~~
To clone the repository but skip initializing submodules, set `submodules` to 'false':
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => latest,
provider => git,
source => 'git://example.com/repo.git',
submodules => false,
}
~~~
To clone the repository and trust the server certificate (sslVerify=false), set `trust_server_cert` to 'true':
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => git,
source => 'git://example.com/repo.git',
trust_server_cert => true,
}
~~~
#### Use multiple remotes with a repository
In place of a single string, you can set `source` to a hash of one or more name => URL pairs:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => git,
remote => 'origin'
source => {
'origin' => 'https://github.com/puppetlabs/puppetlabs-vcsrepo.git',
'other_remote' => 'https://github.com/other_user/puppetlabs-vcsrepo.git'
},
}
~~~
**Note:** If you set `source` to a hash, one of the names you specify must match the value of the `remote` parameter. That remote serves as the upstream of your managed repository.
#### Connect via SSH
To connect to your source repository via SSH (such as 'username@server:…'), we recommend managing your SSH keys with Puppet and using the [`require`](http://docs.puppet.com/references/stable/metaparameter.html#require) metaparameter to make sure they are present before the `vcsrepo` resource is applied.
To use SSH keys associated with a user, specify the username in the `user` parameter:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => latest,
provider => git,
source => 'ssh://username@example.com/repo.git',
user => 'toto', #uses toto's $HOME/.ssh setup
require => File['/home/toto/.ssh/id_rsa'],
}
~~~
To use SSH over a nonstandard port, use the full SSH scheme and include the port number:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => latest,
provider => git,
source => 'ssh://username@example.com:7999/repo.git',
}
~~~
### Bazaar
#### Create a blank repository
To create a blank repository, suitable for use as a central repository, define `vcsrepo` without `source` or `revision`:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => bzr,
}
~~~
#### Branch from an existing repository
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => bzr,
source => '/some/path',
}
~~~
To branch from a specific revision, set `revision` to a valid [Bazaar revision spec](http://wiki.bazaar.canonical.com/BzrRevisionSpec):
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => bzr,
source => '/some/path',
revision => 'menesis@pov.lt-20100309191856-4wmfqzc803fj300x',
}
~~~
#### Connect via SSH
To connect to your source repository via SSH (such as `'bzr+ssh://...'` or `'sftp://...,'`), we recommend using the [`require`](http://docs.puppet.com/references/stable/metaparameter.html#require) metaparameter to make sure your SSH keys are present before the `vcsrepo` resource is applied:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => latest,
provider => bzr,
source => 'bzr+ssh://bzr.example.com/some/path',
user => 'toto', #uses toto's $HOME/.ssh setup
require => File['/home/toto/.ssh/id_rsa'],
}
~~~
### CVS
#### Create a blank repository
To create a blank repository suitable for use as a central repository, define `vcsrepo` without `source` or `revision`:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => cvs,
}
~~~
#### Checkout/update from a repository
~~~ puppet
vcsrepo { '/path/to/workspace':
ensure => present,
provider => cvs,
source => ':pserver:anonymous@example.com:/sources/myproj',
}
~~~
To get a specific module on the current mainline, supply the `module` parameter:
~~~ puppet
vcsrepo { '/vagrant/lockss-daemon-source':
ensure => present,
provider => cvs,
source => ':pserver:anonymous@lockss.cvs.sourceforge.net:/cvsroot/lockss',
module => 'lockss-daemon',
}
~~~
To set the GZIP compression levels for your repository history, use the `compression` parameter:
~~~ puppet
vcsrepo { '/path/to/workspace':
ensure => present,
provider => cvs,
compression => 3,
source => ':pserver:anonymous@example.com:/sources/myproj',
}
~~~
To get a specific revision, set `revision` to the revision number.
~~~ puppet
vcsrepo { '/path/to/workspace':
ensure => present,
provider => cvs,
compression => 3,
source => ':pserver:anonymous@example.com:/sources/myproj',
revision => '1.2',
}
~~~
You can also set `revision` to a tag:
~~~ puppet
vcsrepo { '/path/to/workspace':
ensure => present,
provider => cvs,
compression => 3,
source => ':pserver:anonymous@example.com:/sources/myproj',
revision => 'SOMETAG',
}
~~~
#### Connect via SSH
To connect to your source repository via SSH, we recommend using the [`require`](http://docs.puppet.com/references/stable/metaparameter.html#require) metaparameter to make sure your SSH keys are present before the `vcsrepo` resource is applied:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => latest,
provider => cvs,
source => ':pserver:anonymous@example.com:/sources/myproj',
user => 'toto', #uses toto's $HOME/.ssh setup
require => File['/home/toto/.ssh/id_rsa'],
}
~~~
### Mercurial
#### Create a blank repository
To create a blank repository suitable for use as a central repository, define `vcsrepo` without `source` or `revision`:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => hg,
}
~~~
#### Clone/pull and update a repository
To get the default branch tip:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => hg,
source => 'http://hg.example.com/myrepo',
}
~~~
For a specific changeset, use `revision`:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => hg,
source => 'http://hg.example.com/myrepo',
revision => '21ea4598c962',
}
~~~
You can also set `revision` to a tag:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => hg,
source => 'http://hg.example.com/myrepo',
revision => '1.1.2',
}
~~~
To check out as a specific user:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => hg,
source => 'http://hg.example.com/myrepo',
user => 'user',
}
~~~
To specify an SSH identity key:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => hg,
source => 'ssh://hg@hg.example.com/myrepo',
identity => '/home/user/.ssh/id_dsa1',
}
~~~
To specify a username and password for HTTP Basic authentication:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => latest,
provider => hg,
source => 'http://hg.example.com/myrepo',
basic_auth_username => 'hgusername',
basic_auth_password => 'hgpassword',
}
~~~
#### Connect via SSH
To connect to your source repository via SSH (such as `'ssh://...'`), we recommend using the [`require` metaparameter](http://docs.puppet.com/references/stable/metaparameter.html#require) to make sure your SSH keys are present before the `vcsrepo` resource is applied:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => latest,
provider => hg,
source => 'ssh://hg.example.com//path/to/myrepo',
user => 'toto', #uses toto's $HOME/.ssh setup
require => File['/home/toto/.ssh/id_rsa'],
}
~~~
### Perforce
#### Create an empty workspace
To set up the connection to your Perforce service, set `p4config` to the location of a valid Perforce [config file](http://www.perforce.com/perforce/doc.current/manuals/p4guide/chapter.configuration.html#configuration.process.configfiles) stored on the node:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => p4,
p4config => '/root/.p4config'
}
~~~
**Note:** If you don't include the `P4CLIENT` setting in your config file, the provider generates a workspace name based on the digest of `path` and the node's hostname (such as `puppet-91bc00640c4e5a17787286acbe2c021c`).
#### Create/update and sync a Perforce workspace
To sync a depot path to head, set `ensure` to 'latest':
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => latest,
provider => p4,
source => '//depot/branch/...'
}
~~~
To sync to a specific changelist, specify its revision number with the `revision` parameter:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => p4,
source => '//depot/branch/...',
revision => '2341'
}
~~~
You can also set `revision` to a label:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => p4,
source => '//depot/branch/...',
revision => 'my_label'
}
~~~
### Subversion
#### Create a blank repository
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => svn,
}
~~~
#### Check out from an existing repository
Provide a `source` pointing to the branch or tag you want to check out:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => svn,
source => 'svn://svnrepo/hello/branches/foo',
}
~~~
You can also designate a specific revision:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => svn,
source => 'svn://svnrepo/hello/branches/foo',
revision => '1234',
}
~~~
####Checking out only specific paths
**Note:** The `includes` param is only supported when subversion client version is >= 1.6.
You can check out only specific paths in a particular repository by providing their relative paths to the `includes` parameter, like so:
~~~
vcsrepo { '/path/to/repo':
ensure => present,
provider => svn,
source => 'http://svnrepo/hello/trunk',
includes => [
'root-file.txt',
'checkout-folder',
'file/this-file.txt',
'folder/this-folder/',
]
}
~~~
This will create files `/path/to/repo/file-at-root-path.txt` and `/path/to/repo/file/nested/within/repo.jmx`, with folders `/path/to/repo/some-folder` and `/path/to/repo/nested/folder/to/checkout` completely recreating their corresponding working tree path.
When specified, the `depth` parameter will also be applied to the `includes` -- the root directory will be checked out using an `empty` depth, and the `includes` you specify will be checked out using the `depth` you provide.
To illustrate this point, using the above snippet (with the specified `includes`) and a remote repository layout like this:
~~~
.
├── checkout-folder
│ ├── file1
│ └── nested-1
│ ├── nested-2
│ │ └── nested-file-2
│ └── nested-file-1
├── file
│ ├── NOT-this-file.txt
│ └── this-file.txt
├── folder
│ ├── never-checked-out
│ └── this-folder
│ ├── deep-nested-1
│ │ ├── deep-nested-2
│ │ │ └── deep-nested-file-2
│ │ └── deep-nested-file-1
│ └── this-file.txt
├── NOT-this-file.txt
├── NOT-this-folder
│ ├── NOT-this-file.txt
│ └── NOT-this-one-either.txt
└── root-file.txt
~~~
With no `depth` given, your local folder `/path/to/repo` will look like this:
~~~
.
├── checkout-folder
│ ├── file1
│ └── nested-1
│ ├── nested-2
│ │ └── nested-file-2
│ └── nested-file-1
├── file
│ └── this-file.txt
├── folder
│ └── this-folder
│ ├── deep-nested-1
│ │ ├── deep-nested-2
│ │ │ └── deep-nested-file-2
│ │ └── deep-nested-file-1
│ └── this-file.txt
└── root-file.txt
~~~
And with a `depth` of `files` will look like this:
~~~
.
├── checkout-folder
│ └── file1
├── file
│ └── this-file.txt
├── folder
│ └── this-folder
│ └── this-file.txt
└── root-file.txt
~~~
####Use a specific Subversion configuration directory
Use the `configuration` parameter to designate the directory that contains your Subversion configuration files (typically, '/path/to/.subversion'):
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => present,
provider => svn,
source => 'svn://svnrepo/hello/branches/foo',
configuration => '/path/to/.subversion',
}
~~~
#### Connect via SSH
To connect to your source repository via SSH (such as `'svn+ssh://...'`), we recommend using the [`require` metaparameter](http://docs.puppet.com/references/stable/metaparameter.html#require) to make sure your SSH keys are present before the `vcsrepo` resource is applied:
~~~ puppet
vcsrepo { '/path/to/repo':
ensure => latest,
provider => svn,
source => 'svn+ssh://svnrepo/hello/branches/foo',
user => 'toto', #uses toto's $HOME/.ssh setup
require => File['/home/toto/.ssh/id_rsa'],
}
~~~
## Reference
### Type: vcsrepo
The vcsrepo module adds only one type with several providers. Each provider abstracts a different VCS, and each provider includes a set of features according to its needs.
#### Providers
**Note:** Not all features are available with all providers.
##### `git` - Supports the Git VCS.
Features: `bare_repositories`, `depth`, `multiple_remotes`, `reference_tracking`, `ssh_identity`, `submodules`, `user`
Parameters: `depth`, `ensure`, `excludes`, `force`, `group`, `identity`, `owner`, `path`, `provider`, `remote`, `revision`, `source`, `user`
##### `bzr` - Supports the Bazaar VCS.
Features: `reference_tracking`
Parameters: `ensure`, `excludes`, `force`, `group`, `owner`, `path`, `provider`, `revision`, `source`
##### `cvs` - Supports the CVS VCS.
Features: `cvs_rsh`, `gzip_compression`, `modules`, `reference_tracking`, `user`
Parameters: `compression`, `cvs_rsh`, `ensure`, `excludes`, `force`, `group`, `module`, `owner`, `path`, `provider`
##### `hg` - Supports the Mercurial VCS.
Features: `reference_tracking`, `ssh_identity`, `user`
Parameters: `ensure`, `excludes`, `force`, `group`, `identity`, `owner`, `path`, `provider`, `revision`, `source`, `user`
##### `p4` - Supports the Perforce VCS.
Features: `p4config`, `reference_tracking`
Parameters: `ensure`, `excludes`, `force`, `group`, `owner`, `p4config`, `path`, `provider`, `revision`, `source`
##### `svn` - Supports the Subversion VCS.
Features: `basic_auth`, `configuration`, `conflict`, `depth`, `filesystem_types`, `reference_tracking`
Parameters: `basic_auth_password`, `basic_auth_username`, `configuration`, `conflict`, `ensure`, `excludes`, `force`, `fstype`, `group`, `includes`, `owner`, `path`, `provider`, `revision`, `source`, `trust_server_cert`
#### Features
**Note:** Not all features are available with all providers.
* `bare_repositories` - Differentiates between bare repositories and those with working copies. (Available with `git`.)
-* `basic_auth` - Supports HTTP Basic authentication. (Available with `svn`.)
+* `basic_auth` - Supports HTTP Basic authentication. (Available with `hg` and `svn`.)
* `conflict` - Lets you decide how to resolve any conflicts between the source repository and your working copy. (Available with `svn`.)
* `configuration` - Lets you specify the location of your configuration files. (Available with `svn`.)
* `cvs_rsh` - Understands the `CVS_RSH` environment variable. (Available with `cvs`.)
* `depth` - Supports shallow clones in `git` or sets the scope limit in `svn`. (Available with `git` and `svn`.)
* `filesystem_types` - Supports multiple types of filesystem. (Available with `svn`.)
* `gzip_compression` - Supports explicit GZip compression levels. (Available with `cvs`.)
* `include_paths` - Lets you checkout only certain paths. (Available with `svn`.)
* `modules` - Lets you choose a specific repository module. (Available with `cvs`.)
* `multiple_remotes` - Tracks multiple remote repositories. (Available with `git`.)
* `reference_tracking` - Lets you track revision references that can change over time (e.g., some VCS tags and branch names). (Available with all providers)
* `ssh_identity` - Lets you specify an SSH identity file. (Available with `git` and `hg`.)
* `user` - Can run as a different user. (Available with `git`, `hg` and `cvs`.)
* `p4config` - Supports setting the `P4CONFIG` environment. (Available with `p4`.)
* `submodules` - Supports repository submodules which can be optionally initialized. (Available with `git`.)
#### Parameters
All parameters are optional, except where specified otherwise.
##### `basic_auth_password`
-Specifies the password for HTTP Basic authentication. (Requires the `basic_auth` feature.) Valid options: a string. Default: none.
+Specifies the password for HTTP Basic authentication. (Requires the `basic_auth` feature.) Valid options: a string or Sensitive string. Default: none.
##### `basic_auth_username`
Specifies the username for HTTP Basic authentication. (Requires the `basic_auth` feature.) Valid options: a string. Default: none.
##### `compression`
Sets the GZIP compression level for the repository history. (Requires the `gzip_compression` feature.) Valid options: an integer between 0 and 6. Default: none.
##### `configuration`
Sets the configuration directory to use. (Requires the `configuration` feature.) Valid options: a string containing an absolute path. Default: none.
##### `conflict`
Tells Subversion how to resolve any conflicts between the source repository and your working copy. (Requires the `conflict` feature.) Valid options: 'base', 'mine-full', 'theirs-full', and 'working'. Default: none.
##### `cvs_rsh`
Provides a value for the `CVS_RSH` environment variable. (Requires the `cvs_rsh` feature.) Valid options: a string. Default: none.
##### `depth`
In `git`, `depth` sets the number of commits to include when creating a shallow clone. (Requires the `depth` feature.) Valid options: an integer. Default: none.
In `svn`, `depth` limits the scope of an operation to the specified tree depth. (Requires the `depth` feature.) Valid options: 'empty', 'files', 'immediates', 'infinity'. Default: none.
##### `ensure`
Specifies whether the repository should exist. Valid options: 'present', 'bare', 'absent', and 'latest'. Default: 'present'.
##### `excludes`
Lists any files the repository shouldn't track (similar to .gitignore). Valid options: a string (separate multiple values with the newline character). Default: none.
##### `force`
Specifies whether to delete any existing files in the repository path if creating a new repository. **Use with care.** Valid options: 'true' and 'false'. Default: 'false'.
##### `fstype`
Sets the filesystem type. (Requires the `filesystem_types` feature.) Valid options: 'fsfs' or 'bdb'. Default: none.
##### `group`
Specifies a group to own the repository files. Valid options: a string containing a group name or GID. Default: none.
##### `identity`
Specifies an identity file to use for SSH authentication. (Requires the `ssh_identity` feature.) Valid options: a string containing an absolute path. Default: none.
##### `includes`
Tells Subversion which paths should be checked out at the specified depth; all other paths are not checked out. Default: none (checkout all paths).
##### `module`
Specifies the repository module to manage. (Requires the `modules` feature.) Valid options: a string containing the name of a CVS module. Default: none.
##### `owner`
Specifies a user to own the repository files. Valid options: a string containing a username or UID. Default: none.
##### `p4config`
Specifies a config file that contains settings for connecting to the Perforce service. (Requires the `p4config` feature.) Valid options: a string containing the absolute path to a valid [Perforce config file](http://www.perforce.com/perforce/doc.current/manuals/p4guide/chapter.configuration.html#configuration.process.configfiles). Default: none.
##### `path`
Specifies a location for the managed repository. Valid options: a string containing an absolute path. Default: the title of your declared resource.
##### `provider`
*Required.* Specifies the backend to use for this vcsrepo resource. Valid options: 'bzr', 'cvs', 'git', 'hg', 'p4', and 'svn'.
##### `remote`
Specifies the remote repository to track. (Requires the `multiple_remotes` feature.) Valid options: a string containing one of the remote names specified in `source`. Default: 'origin'.
##### `revision`
Sets the revision of the repository. Valid options vary by provider:
* `git` - A string containing a Git branch name, or a commit SHA or tag.
* `bzr` - A string containing a Bazaar [revision spec](http://wiki.bazaar.canonical.com/BzrRevisionSpec).
* `cvs` - A string containing a CVS [tag or revision number](http://www.thathost.com/wincvs-howto/cvsdoc/cvs_4.html).
* `hg` - A string containing a Mercurial [changeset ID](https://www.mercurial-scm.org/wiki/ChangeSetID) or [tag](https://www.mercurial-scm.org/wiki/Tag).
* `p4` - A string containing a Perforce [change number, label name, client name, or date spec](http://www.perforce.com/perforce/r12.1/manuals/cmdref/o.fspecs.html).
* `svn` - A string containing a Subversion [revision number](http://svnbook.red-bean.com/en/1.7/svn.basic.in-action.html#svn.basic.in-action.revs), [revision keyword, or revision date](http://svnbook.red-bean.com/en/1.7/svn.tour.revs.specifiers.html).
Default: none.
##### `source`
Specifies a source repository to serve as the upstream for your managed repository. Default: none. Valid options vary by provider:
* `git` - A string containing a [Git repository URL](https://www.kernel.org/pub/software/scm/git/docs/git-clone.html#_git_urls_a_id_urls_a) or a hash of `name => URL` mappings. See also [`remote`](#remote).
* `bzr` - A string containing a Bazaar branch location.
* `cvs` - A string containing a CVS root.
* `hg` - A string containing the local path or URL of a Mercurial repository.
* `p4` - A string containing a Perforce depot path.
* `svn` - A string containing a Subversion repository URL.
Default: none.
##### `submodules`
Specifies whether to initialize and update each submodule in the repository. (Requires the `submodules` feature.) Valid options: 'true' and 'false'. Default: 'true'.
##### `trust_server_cert`
Instructs Subversion to accept SSL server certificates issued by unknown certificate authorities. Valid options: 'true' and 'false'. Default: 'false'.
##### `user`
Specifies the user to run as for repository operations. (Requires the `user` feature.) Valid options: a string containing a username or UID. Default: none.
## Limitations
Git is the only VCS provider officially [supported by Puppet Inc.](https://forge.puppet.com/supported) Git with 3.18 changes the maximum enabled TLS protocol version, this breaks some HTTPS functionality on older operating systems. They are Enterprise Linux 5 and OracleLinux 6.
The includes parameter is only supported when SVN client version is >= 1.6.
For an extensive list of supported operating systems, see [metadata.json](https://github.com/puppetlabs/puppetlabs-vcsrepo/blob/master/metadata.json)
## Development
Puppet Inc. modules on the Puppet Forge are open projects, and community contributions are essential for keeping them great. We can't access the huge number of platforms and myriad of hardware, software, and deployment configurations that Puppet is intended to serve.
We want to keep it as easy as possible to contribute changes so that our modules work in your environment. There are a few guidelines that we need contributors to follow so that we can have a chance of keeping on top of things.
You can read the complete module contribution guide [on the Puppet documentation site.](https://docs.puppet.com/guides/module_guides/bgtm.html)
diff --git a/lib/puppet/provider/vcsrepo/hg.rb b/lib/puppet/provider/vcsrepo/hg.rb
index f73fbd0..3df61b0 100644
--- a/lib/puppet/provider/vcsrepo/hg.rb
+++ b/lib/puppet/provider/vcsrepo/hg.rb
@@ -1,147 +1,153 @@
require File.join(File.dirname(__FILE__), '..', 'vcsrepo')
Puppet::Type.type(:vcsrepo).provide(:hg, parent: Puppet::Provider::Vcsrepo) do
desc 'Supports Mercurial repositories'
commands hg: 'hg'
has_features :reference_tracking, :ssh_identity, :user, :basic_auth
def create
check_force
if !@resource.value(:source)
create_repository(@resource.value(:path))
else
clone_repository(@resource.value(:revision))
end
update_owner
end
def working_copy_exists?
return false unless File.directory?(@resource.value(:path))
begin
hg_wrapper('status', @resource.value(:path))
return true
rescue Puppet::ExecutionFailure
return false
end
end
def exists?
working_copy_exists?
end
def destroy
FileUtils.rm_rf(@resource.value(:path))
end
def latest?
at_path do
return revision == latest
end
end
def latest
at_path do
begin
hg_wrapper('incoming', '--branch', '.', '--newest-first', '--limit', '1', remote: true)[%r{^changeset:\s+(?:-?\d+):(\S+)}m, 1]
rescue Puppet::ExecutionFailure
# If there are no new changesets, return the current nodeid
revision
end
end
end
def revision
at_path do
current = hg_wrapper('parents')[%r{^changeset:\s+(?:-?\d+):(\S+)}m, 1]
desired = @resource.value(:revision)
if desired
# Return the tag name if it maps to the current nodeid
mapped = hg_wrapper('tags')[%r{^#{Regexp.quote(desired)}\s+\d+:(\S+)}m, 1]
if current == mapped
desired
else
current
end
else
current
end
end
end
def revision=(desired)
at_path do
begin
hg_wrapper('pull', remote: true)
rescue StandardError
next
end
begin
hg_wrapper('merge')
rescue Puppet::ExecutionFailure
next
# If there's nothing to merge, just skip
end
hg_wrapper('update', '--clean', '-r', desired)
end
update_owner
end
def source
at_path do
hg_wrapper('paths')[%r{^default = (.*)}, 1]
end
end
def source=(_desired)
create # recreate
end
private
def create_repository(path)
hg_wrapper('init', path)
end
def clone_repository(revision)
args = ['clone']
if revision
args.push('-u', revision)
end
args.push(@resource.value(:source),
@resource.value(:path))
args.push(remote: true)
hg_wrapper(*args)
end
def update_owner
set_ownership if @resource.value(:owner) || @resource.value(:group)
end
+ def sensitive?
+ (@resource.parameters.key?(:basic_auth_password) && @resource.parameters[:basic_auth_password].sensitive) ? true : false # Check if there is a sensitive parameter
+ end
+
def hg_wrapper(*args)
options = { remote: false }
if !args.empty? && args[-1].is_a?(Hash)
options.merge!(args.pop)
end
if @resource.value(:basic_auth_username) && @resource.value(:basic_auth_password)
args += [
'--config', "auth.x.prefix=#{@resource.value(:source)}",
'--config', "auth.x.username=#{@resource.value(:basic_auth_username)}",
- '--config', "auth.x.password=#{@resource.value(:basic_auth_password)}",
+ '--config', "auth.x.password=#{sensitive? ? @resource.value(:basic_auth_password).unwrap : @resource.value(:basic_auth_password)}",
'--config', 'auth.x.schemes=http https'
]
end
if options[:remote] && @resource.value(:identity)
args += ['--ssh', "ssh -oStrictHostKeyChecking=no -oPasswordAuthentication=no -oKbdInteractiveAuthentication=no -oChallengeResponseAuthentication=no -i #{@resource.value(:identity)}"]
end
+
+ args.map! { |a| (a =~ %r{\s}) ? "'#{a}'" : a } # Adds quotes to arguments with whitespaces.
+
if @resource.value(:user) && @resource.value(:user) != Facter['id'].value
- args.map! { |a| (a =~ %r{\s}) ? "'#{a}'" : a } # Adds quotes to arguments with whitespaces.
- Puppet::Util::Execution.execute("hg #{args.join(' ')}", uid: @resource.value(:user), failonfail: true, combine: true)
+ Puppet::Util::Execution.execute("hg #{args.join(' ')}", uid: @resource.value(:user), failonfail: true, combine: true, sensitive: sensitive?)
else
- hg(*args)
+ Puppet::Util::Execution.execute("hg #{args.join(' ')}", sensitive: sensitive?)
end
end
end
diff --git a/lib/puppet/provider/vcsrepo/svn.rb b/lib/puppet/provider/vcsrepo/svn.rb
index 5fd3670..3e3d898 100644
--- a/lib/puppet/provider/vcsrepo/svn.rb
+++ b/lib/puppet/provider/vcsrepo/svn.rb
@@ -1,294 +1,302 @@
require File.join(File.dirname(__FILE__), '..', 'vcsrepo')
Puppet::Type.type(:vcsrepo).provide(:svn, parent: Puppet::Provider::Vcsrepo) do
desc 'Supports Subversion repositories'
commands svn: 'svn',
svnadmin: 'svnadmin',
svnlook: 'svnlook'
has_features :filesystem_types, :reference_tracking, :basic_auth, :configuration, :conflict, :depth,
:include_paths
def create
check_force
if !@resource.value(:source)
if @resource.value(:includes)
raise Puppet::Error, 'Specifying include paths on a nonexistent repo.'
end
create_repository(@resource.value(:path))
else
if @resource.value(:basic_auth_username) && !@resource.value(:basic_auth_password)
raise("You must specify the HTTP basic authentication password for user '#{@resource.value(:basic_auth_username)}'")
end
if !@resource.value(:basic_auth_username) && @resource.value(:basic_auth_password)
raise('You must specify the HTTP basic authentication username')
end
checkout_repository(@resource.value(:source),
@resource.value(:path),
@resource.value(:revision),
@resource.value(:depth))
end
if @resource.value(:includes)
validate_version
update_includes(@resource.value(:includes))
end
update_owner
end
def working_copy_exists?
return false unless File.directory?(@resource.value(:path))
if @resource.value(:source)
begin
- svn('info', @resource.value(:path))
+ svn_wrapper('info', @resource.value(:path))
return true
rescue Puppet::ExecutionFailure => detail
if detail.message =~ %r{This client is too old}
raise Puppet::Error, detail.message
end
return false
end
else
begin
svnlook('uuid', @resource.value(:path))
return true
rescue Puppet::ExecutionFailure
return false
end
end
end
def exists?
working_copy_exists?
end
def destroy
FileUtils.rm_rf(@resource.value(:path))
end
def latest?
at_path do
(revision >= latest) && (@resource.value(:source) == source)
end
end
def buildargs
args = ['--non-interactive']
if @resource.value(:basic_auth_username) && @resource.value(:basic_auth_password)
args.push('--username', @resource.value(:basic_auth_username))
- args.push('--password', @resource.value(:basic_auth_password))
+ args.push('--password', sensitive? ? @resource.value(:basic_auth_password).unwrap : @resource.value(:basic_auth_password))
args.push('--no-auth-cache')
end
if @resource.value(:configuration)
args.push('--config-dir', @resource.value(:configuration))
end
if @resource.value(:trust_server_cert) != :false
args.push('--trust-server-cert')
end
args
end
def latest
args = buildargs.push('info', '-r', 'HEAD')
at_path do
- svn(*args)[%r{^Revision:\s+(\d+)}m, 1]
+ svn_wrapper(*args)[%r{^Revision:\s+(\d+)}m, 1]
end
end
def source
args = buildargs.push('info')
at_path do
- svn(*args)[%r{^URL:\s+(\S+)}m, 1]
+ svn_wrapper(*args)[%r{^URL:\s+(\S+)}m, 1]
end
end
def source=(desired)
args = buildargs.push('switch')
if @resource.value(:force)
args.push('--force')
end
if @resource.value(:revision)
args.push('-r', @resource.value(:revision))
end
if @resource.value(:conflict)
args.push('--accept', @resource.value(:conflict))
end
args.push(desired)
at_path do
- svn(*args)
+ svn_wrapper(*args)
end
update_owner
end
def revision
args = buildargs.push('info')
at_path do
- svn(*args)[%r{^Revision:\s+(\d+)}m, 1]
+ svn_wrapper(*args)[%r{^Revision:\s+(\d+)}m, 1]
end
end
def revision=(desired)
args = if @resource.value(:source)
buildargs.push('switch', '-r', desired, @resource.value(:source))
else
buildargs.push('update', '-r', desired)
end
if @resource.value(:force)
args.push('--force')
end
if @resource.value(:conflict)
args.push('--accept', @resource.value(:conflict))
end
at_path do
- svn(*args)
+ svn_wrapper(*args)
end
update_owner
end
def includes
return nil if Gem::Version.new(return_svn_client_version) < Gem::Version.new('1.6.0')
get_includes('.')
end
def includes=(desired)
validate_version
exists = includes
old_paths = exists - desired
new_paths = desired - exists
# Remove paths that are no longer specified
old_paths.each { |path| delete_include(path) }
update_includes(new_paths)
end
private
+ def svn_wrapper(*args)
+ Puppet::Util::Execution.execute("svn #{args.join(' ')}", sensitive: sensitive?)
+ end
+
+ def sensitive?
+ (@resource.parameters.key?(:basic_auth_password) && @resource.parameters[:basic_auth_password].sensitive) ? true : false # Check if there is a sensitive parameter
+ end
+
def get_includes(directory)
at_path do
args = buildargs.push('info', directory)
- if svn(*args)[%r{^Depth:\s+(\w+)}m, 1] != 'empty'
+ if svn_wrapper(*args)[%r{^Depth:\s+(\w+)}m, 1] != 'empty'
return directory[2..-1].gsub(File::SEPARATOR, '/')
end
Dir.entries(directory).map { |entry|
next if ['.', '..', '.svn'].include?(entry)
entry = File.join(directory, entry)
if File.directory?(entry)
get_includes(entry)
elsif File.file?(entry)
entry[2..-1].gsub(File::SEPARATOR, '/')
end
}.flatten.compact!
end
end
def delete_include(path)
at_path do
# svn version 1.6 has an incorrect implementation of the `exclude`
# parameter to `--set-depth`; it doesn't handle files, only
# directories. I know, I rolled my eyes, too.
svn_ver = return_svn_client_version
if Gem::Version.new(svn_ver) < Gem::Version.new('1.7.0') && !File.directory?(path)
# In the non-happy case, we delete the file, and check if the only
# thing left in that directory is the .svn folder. If that's the case,
# the loop below will take care of excluding the parent directory, and
# we're back to a happy case. But, if that's not the case, we need to
# fire off a warning telling the user the path can't be excluded.
Puppet.debug "Vcsrepo[#{@resource.name}]: Need to handle #{path} removal specially"
File.delete(path)
if Dir.entries(File.dirname(path)).sort != ['.', '..', '.svn']
Puppet.warning "Unable to exclude #{path} from Vcsrepo[#{@resource.name}]; update to subversion >= 1.7"
end
else
Puppet.debug "Vcsrepo[#{@resource.name}]: Can remove #{path} directly using svn"
args = buildargs.push('update', '--set-depth', 'exclude', path)
- svn(*args)
+ svn_wrapper(*args)
end
# Keep walking up the parent directories of this include until we find
# a non-empty folder, excluding as we go.
while (path = path.rpartition(File::SEPARATOR)[0]) != ''
entries = Dir.entries(path).sort
break if entries != ['.', '..'] && entries != ['.', '..', '.svn']
args = buildargs.push('update', '--set-depth', 'exclude', path)
- svn(*args)
+ svn_wrapper(*args)
end
end
end
def checkout_repository(source, path, revision, depth)
args = buildargs.push('checkout')
if revision
args.push('-r', revision)
end
if @resource.value(:includes)
# Make root checked out at empty depth to provide sparse directories
args.push('--depth', 'empty')
elsif depth
args.push('--depth', depth)
end
args.push(source, path)
- svn(*args)
+ svn_wrapper(*args)
end
def create_repository(path)
args = ['create']
if @resource.value(:fstype)
args.push('--fs-type', @resource.value(:fstype))
end
args << path
svnadmin(*args)
end
def update_owner
set_ownership if @resource.value(:owner) || @resource.value(:group)
end
def update_includes(paths)
at_path do
args = buildargs.push('update')
args.push('--depth', 'empty')
if @resource.value(:revision)
args.push('-r', @resource.value(:revision))
end
parents = paths.map { |path| File.dirname(path) }
parents = make_include_paths(parents)
args.push(*parents)
- svn(*args)
+ svn_wrapper(*args)
args = buildargs.push('update')
if @resource.value(:revision)
args.push('-r', @resource.value(:revision))
end
if @resource.value(:depth)
args.push('--depth', @resource.value(:depth))
end
args.push(*paths)
- svn(*args)
+ svn_wrapper(*args)
end
end
def make_include_paths(includes)
includes.map { |inc|
prefix = nil
inc.split('/').map do |path|
prefix = [prefix, path].compact.join('/')
end
}.flatten
end
def return_svn_client_version
Facter.value('vcsrepo_svn_ver').dup
end
def validate_version
svn_ver = return_svn_client_version
raise "Includes option is not available for SVN versions < 1.6. Version installed: #{svn_ver}" if Gem::Version.new(svn_ver) < Gem::Version.new('1.6.0')
end
end
diff --git a/lib/puppet/type/vcsrepo.rb b/lib/puppet/type/vcsrepo.rb
index fa6e1b8..808ebc4 100644
--- a/lib/puppet/type/vcsrepo.rb
+++ b/lib/puppet/type/vcsrepo.rb
@@ -1,295 +1,306 @@
require 'pathname'
require 'puppet/parameter/boolean'
Puppet::Type.newtype(:vcsrepo) do
desc 'A local version control repository'
feature :gzip_compression,
'The provider supports explicit GZip compression levels'
feature :basic_auth,
'The provider supports HTTP Basic Authentication'
feature :bare_repositories,
"The provider differentiates between bare repositories
and those with working copies",
methods: [:bare_exists?, :working_copy_exists?]
feature :filesystem_types,
'The provider supports different filesystem types'
feature :reference_tracking,
"The provider supports tracking revision references that can change
over time (eg, some VCS tags and branch names)"
feature :ssh_identity,
'The provider supports a configurable SSH identity file'
feature :user,
'The provider can run as a different user'
feature :modules,
'The repository contains modules that can be chosen of'
feature :multiple_remotes,
'The repository tracks multiple remote repositories'
feature :configuration,
'The configuration directory to use'
feature :cvs_rsh,
'The provider understands the CVS_RSH environment variable'
feature :depth,
'The provider can do shallow clones or set scope limit'
feature :branch,
'The name of the branch'
feature :p4config,
'The provider understands Perforce Configuration'
feature :submodules,
'The repository contains submodules which can be optionally initialized'
feature :conflict,
'The provider supports automatic conflict resolution'
feature :include_paths,
'The provider supports checking out only specific paths'
ensurable do
attr_accessor :latest
def insync?(is)
@should ||= []
case should
when :present
return true unless [:absent, :purged, :held].include?(is)
when :latest
return true if is == :latest
return false
when :bare
return is == :bare
when :mirror
return is == :mirror
when :absent
return is == :absent
end
end
newvalue :present do
if !provider.exists?
provider.create
elsif provider.class.feature?(:bare_repositories) && provider.bare_exists?
provider.convert_bare_to_working_copy
end
end
newvalue :bare, required_features: [:bare_repositories] do
if !provider.exists?
provider.create
elsif provider.working_copy_exists?
provider.convert_working_copy_to_bare
elsif provider.mirror?
provider.set_no_mirror
end
end
newvalue :mirror, required_features: [:bare_repositories] do
if !provider.exists?
provider.create
elsif provider.working_copy_exists?
provider.convert_working_copy_to_bare
elsif !provider.mirror?
provider.set_mirror
end
end
newvalue :absent do
provider.destroy
end
newvalue :latest, required_features: [:reference_tracking] do
if provider.exists? && !@resource.value(:force)
if provider.class.feature?(:bare_repositories) && provider.bare_exists?
provider.convert_bare_to_working_copy
end
if provider.respond_to?(:update_references)
provider.update_references
end
reference = if provider.respond_to?(:latest?)
provider.latest || provider.revision
else
resource.value(:revision) || provider.revision
end
notice "Updating to latest '#{reference}' revision"
provider.revision = reference
else
notice 'Creating repository from latest'
provider.create
end
end
def retrieve
prov = @resource.provider
raise Puppet::Error, 'Could not find provider' unless prov
if prov.working_copy_exists?
(@should.include?(:latest) && prov.latest?) ? :latest : :present
elsif prov.class.feature?(:bare_repositories) && prov.bare_exists?
if prov.mirror?
:mirror
else
:bare
end
else
:absent
end
end
end
newparam :path do
desc 'Absolute path to repository'
isnamevar
validate do |value|
path = Pathname.new(value)
unless path.absolute?
raise ArgumentError, "Path must be absolute: #{path}"
end
end
end
newproperty :source do
desc 'The source URI for the repository'
# Tolerate versions/providers that strip/add trailing slashes
def insync?(is)
# unwrap @should
should = @should[0]
return true if is == should
begin
if should[-1] == '/'
return true if is == should[0..-2]
elsif is[-1] == '/'
return true if is[0..-2] == should
end
rescue StandardError
return
end
false
end
end
newparam :fstype, required_features: [:filesystem_types] do
desc 'Filesystem type'
end
newproperty :revision do
desc 'The revision of the repository'
newvalue(%r{^\S+$})
end
newparam :owner do
desc 'The user/uid that owns the repository files'
end
newparam :group do
desc 'The group/gid that owns the repository files'
end
newparam :user do
desc 'The user to run for repository operations'
end
newparam :excludes do
desc "Local paths which shouldn't be tracked by the repository"
end
newproperty :includes, required_features: [:include_paths], array_matching: :all do
desc 'Paths to be included from the repository'
def insync?(is)
if is.is_a?(Array) && @should.is_a?(Array)
is.sort == @should.sort
else
is == @should
end
end
validate do |path|
raise Puppet::Error, "Include path '#{path}' starts with a '/'; remove it" if path[0..0] == '/'
super(path)
end
end
newparam(:force, boolean: true, parent: Puppet::Parameter::Boolean) do
desc 'Force repository creation, destroying any files on the path in the process.'
defaultto false
end
newparam :compression, required_features: [:gzip_compression] do
desc 'Compression level'
validate do |amount|
unless Integer(amount).between?(0, 6)
raise ArgumentError, "Unsupported compression level: #{amount} (expected 0-6)"
end
end
end
newparam :basic_auth_username, required_features: [:basic_auth] do
desc 'HTTP Basic Auth username'
end
newparam :basic_auth_password, required_features: [:basic_auth] do
desc 'HTTP Basic Auth password'
end
newparam :identity, required_features: [:ssh_identity] do
desc 'SSH identity file'
end
newproperty :module, required_features: [:modules] do
desc 'The repository module to manage'
end
newparam :remote, required_features: [:multiple_remotes] do
desc 'The remote repository to track'
defaultto 'origin'
end
newparam :configuration, required_features: [:configuration] do
desc 'The configuration directory to use'
end
newparam :cvs_rsh, required_features: [:cvs_rsh] do
desc 'The value to be used for the CVS_RSH environment variable.'
end
newparam :depth, required_features: [:depth] do
desc 'The value to be used to do a shallow clone.'
end
newparam :branch, required_features: [:branch] do
desc 'The name of the branch to clone.'
end
newparam :p4config, required_features: [:p4config] do
desc 'The Perforce P4CONFIG environment.'
end
newparam :submodules, required_features: [:submodules] do
desc 'Initialize and update each submodule in the repository.'
newvalues(true, false)
defaultto true
end
newparam :conflict do
desc 'The action to take if conflicts exist between repository and working copy'
end
newparam :trust_server_cert do
desc 'Trust server certificate'
newvalues(true, false)
defaultto :false
end
autorequire(:package) do
['git', 'git-core', 'mercurial', 'subversion']
end
+
+ private
+
+ def set_sensitive_parameters(sensitive_parameters) # rubocop:disable Style/AccessorMethodName
+ if sensitive_parameters.include?(:basic_auth_password)
+ sensitive_parameters.delete(:basic_auth_password)
+ parameter(:basic_auth_password).sensitive = true
+ end
+
+ super(sensitive_parameters)
+ end
end
diff --git a/spec/unit/puppet/provider/vcsrepo/hg_spec.rb b/spec/unit/puppet/provider/vcsrepo/hg_spec.rb
index ff39fd7..b0ebb0b 100644
--- a/spec/unit/puppet/provider/vcsrepo/hg_spec.rb
+++ b/spec/unit/puppet/provider/vcsrepo/hg_spec.rb
@@ -1,139 +1,163 @@
require 'spec_helper'
describe Puppet::Type.type(:vcsrepo).provider(:hg) do
let(:resource) do
Puppet::Type.type(:vcsrepo).new(name: 'test',
ensure: :present,
provider: :hg,
path: '/tmp/vcsrepo')
end
let(:provider) { resource.provider }
before :each do
allow(Puppet::Util).to receive(:which).with('hg').and_return('/usr/bin/hg')
end
describe 'creating' do
context 'with source and revision' do
it "executes 'hg clone -u' with the revision" do
resource[:source] = 'something'
resource[:revision] = '1'
- expect(provider).to receive(:hg).with('clone', '-u', resource.value(:revision),
- resource.value(:source), resource.value(:path))
+ expect(Puppet::Util::Execution).to receive(:execute).with("hg clone -u #{resource.value(:revision)} #{resource.value(:source)} #{resource.value(:path)}", sensitive: false)
provider.create
end
end
context 'without revision' do
it "justs execute 'hg clone' without a revision" do
resource[:source] = 'something'
- expect(provider).to receive(:hg).with('clone', resource.value(:source), resource.value(:path))
+ expect(Puppet::Util::Execution).to receive(:execute).with("hg clone #{resource.value(:source)} #{resource.value(:path)}", sensitive: false)
provider.create
end
end
context 'when a source is not given' do
it "executes 'hg init'" do
- expect(provider).to receive(:hg).with('init', resource.value(:path))
+ expect(Puppet::Util::Execution).to receive(:execute).with("hg init #{resource.value(:path)}", sensitive: false)
provider.create
end
end
context 'when basic auth is used' do
it "executes 'hg clone'" do
resource[:source] = 'something'
resource[:basic_auth_username] = 'user'
resource[:basic_auth_password] = 'pass'
- expect(provider).to receive(:hg).with('clone', resource.value(:source), resource.value(:path), '--config',
- 'auth.x.prefix=' + resource.value(:source), '--config', 'auth.x.username=' + resource.value(:basic_auth_username),
- '--config', 'auth.x.password=' + resource.value(:basic_auth_password), '--config', 'auth.x.schemes=http https')
+
+ command = "hg clone #{resource.value(:source)} #{resource.value(:path)} --config auth.x.prefix=#{resource.value(:source)} "\
+ "--config auth.x.username=#{resource.value(:basic_auth_username)} --config auth.x.password=#{resource.value(:basic_auth_password)} "\
+ "--config 'auth.x.schemes=http https'"\
+
+ expect(Puppet::Util::Execution).to receive(:execute).with(command, sensitive: false)
+ provider.create
+ end
+ end
+
+ context 'when basic auth is used with Sensitive basic_auth_password' do
+ let(:resource) do
+ Puppet::Type.type(:vcsrepo).new(name: 'test',
+ ensure: :present,
+ provider: :hg,
+ path: '/tmp/vcsrepo',
+ source: 'something',
+ sensitive_parameters: [:basic_auth_password],
+ basic_auth_username: 'user',
+ basic_auth_password: Puppet::Pops::Types::PSensitiveType::Sensitive.new('pass'))
+ end
+
+ it "executes 'hg clone'" do
+ command = "hg clone #{resource.value(:source)} #{resource.value(:path)} --config auth.x.prefix=#{resource.value(:source)} "\
+ "--config auth.x.username=#{resource.value(:basic_auth_username)} --config auth.x.password=#{resource.value(:basic_auth_password).unwrap} "\
+ "--config 'auth.x.schemes=http https'"\
+
+ expect(Puppet::Util::Execution).to receive(:execute).with(command, sensitive: true)
provider.create
end
end
end
describe 'destroying' do
it 'removes the directory' do
expect_rm_rf
provider.destroy
end
end
describe 'checking existence' do
it 'checks for the directory' do
expect_directory?(true, resource.value(:path))
- expect(provider).to receive(:hg).with('status', resource.value(:path))
+ expect(Puppet::Util::Execution).to receive(:execute).with("hg status #{resource.value(:path)}", sensitive: false)
provider.exists?
end
end
describe 'checking the revision property' do
before(:each) do
expect_chdir
end
context 'when given a non-SHA as the resource revision' do
before(:each) do
- allow(provider).to receive(:hg).with('parents').and_return(fixture(:hg_parents))
- allow(provider).to receive(:hg).with('tags').and_return(fixture(:hg_tags))
+ allow(Puppet::Util::Execution).to receive(:execute).with('hg parents', sensitive: false).and_return(fixture(:hg_parents))
+ allow(Puppet::Util::Execution).to receive(:execute).with('hg tags', sensitive: false).and_return(fixture(:hg_tags))
end
it 'when its sha is not different from the current SHA it and_return the ref' do
resource[:revision] = '0.6'
expect(provider.revision).to eq('0.6')
end
it 'when its SHA is different than the current SHA it and_return the current SHA' do
resource[:revision] = '0.5.3'
expect(provider.revision).to eq('34e6012c783a')
end
end
context 'when given a SHA as the resource revision' do
before(:each) do
- allow(provider).to receive(:hg).with('parents').and_return(fixture(:hg_parents))
+ allow(Puppet::Util::Execution).to receive(:execute).with('hg parents', sensitive: false).and_return(fixture(:hg_parents))
end
it 'when it is the same as the current SHA it and_return it' do
resource[:revision] = '34e6012c783a'
- expect(provider).to receive(:hg).with('tags').and_return(fixture(:hg_tags))
+ expect(Puppet::Util::Execution).to receive(:execute).with('hg tags', sensitive: false).and_return(fixture(:hg_tags))
expect(provider.revision).to eq(resource.value(:revision))
end
it 'when it is not the same as the current SHA it and_return the current SHA' do
resource[:revision] = 'not-the-same'
- expect(provider).to receive(:hg).with('tags').and_return(fixture(:hg_tags))
+ expect(Puppet::Util::Execution).to receive(:execute).with('hg tags', sensitive: false).and_return(fixture(:hg_tags))
expect(provider.revision).to eq('34e6012c783a')
end
end
end
describe 'setting the revision property' do
let(:revision) { '6aa99e9b3ab1' }
it "uses 'hg update ---clean -r'" do
expect_chdir
- expect(provider).to receive(:hg).with('pull')
- expect(provider).to receive(:hg).with('merge')
- expect(provider).to receive(:hg).with('update', '--clean', '-r', revision)
+ expect(provider).to receive(:hg_wrapper).with('pull', remote: true)
+ expect(provider).to receive(:hg_wrapper).with('merge')
+ expect(provider).to receive(:hg_wrapper).with('update', '--clean', '-r', revision)
provider.revision = revision
end
end
describe 'checking the source property' do
it 'and_return the default path' do
resource[:source] = 'http://selenic.com/hg'
expect_chdir
expect(provider).to receive(:hg_wrapper).with('paths').and_return('default = http://selenic.com/hg')
expect(provider.source).to eq(resource.value(:source))
end
end
describe 'setting the source property' do
it "calls 'create'" do
resource[:source] = 'some-example'
expect(provider).to receive(:create)
provider.source = resource.value(:source)
end
end
end
diff --git a/spec/unit/puppet/provider/vcsrepo/svn_spec.rb b/spec/unit/puppet/provider/vcsrepo/svn_spec.rb
index a8bfa48..450fd93 100644
--- a/spec/unit/puppet/provider/vcsrepo/svn_spec.rb
+++ b/spec/unit/puppet/provider/vcsrepo/svn_spec.rb
@@ -1,289 +1,308 @@
require 'spec_helper'
describe Puppet::Type.type(:vcsrepo).provider(:svn) do
let(:resource) do
Puppet::Type.type(:vcsrepo).new(name: 'test',
ensure: :present,
provider: :svn,
path: '/tmp/vcsrepo')
end
let(:provider) { resource.provider }
let(:test_paths) { ['path1/file1', 'path2/nested/deep/file2'] }
let(:test_paths_parents) { ['path1', 'path2', 'path2/nested', 'path2/nested/deep'] }
before :each do
allow(Puppet::Util).to receive(:which).with('svn').and_return('/usr/bin/svn')
end
describe 'creation/checkout' do
context 'with source and revision' do
it "executes 'svn checkout' with a revision" do
resource[:source] = 'exists'
resource[:revision] = '1'
- expect(provider).to receive(:svn).with('--non-interactive', 'checkout', '-r', resource.value(:revision),
- resource.value(:source), resource.value(:path))
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'checkout', '-r', resource.value(:revision),
+ resource.value(:source), resource.value(:path))
provider.create
end
end
context 'with source' do
it "justs execute 'svn checkout' without a revision" do
resource[:source] = 'exists'
- expect(provider).to receive(:svn).with('--non-interactive', 'checkout',
- resource.value(:source),
- resource.value(:path))
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'checkout',
+ resource.value(:source),
+ resource.value(:path))
provider.create
end
end
context 'with fstype' do
it "executes 'svnadmin create' with an '--fs-type' option" do
resource[:fstype] = 'ext4'
expect(provider).to receive(:svnadmin).with('create', '--fs-type',
resource.value(:fstype),
resource.value(:path))
provider.create
end
end
context 'without fstype' do
it "executes 'svnadmin create' without an '--fs-type' option" do
expect(provider).to receive(:svnadmin).with('create', resource.value(:path))
provider.create
end
end
context 'with depth' do
it "executes 'svn checkout' with a depth" do
resource[:source] = 'exists'
resource[:depth] = 'infinity'
- expect(provider).to receive(:svn).with('--non-interactive', 'checkout', '--depth', 'infinity',
- resource.value(:source), resource.value(:path))
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'checkout', '--depth', 'infinity',
+ resource.value(:source), resource.value(:path))
provider.create
end
end
context 'with trust_server_cert' do
it "executes 'svn checkout' without a trust-server-cert" do
resource[:source] = 'exists'
resource[:trust_server_cert] = false
- expect(provider).to receive(:svn).with('--non-interactive', 'checkout',
- resource.value(:source), resource.value(:path))
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'checkout',
+ resource.value(:source), resource.value(:path))
provider.create
end
it "executes 'svn checkout' with a trust-server-cert" do
resource[:source] = 'exists'
resource[:trust_server_cert] = true
- expect(provider).to receive(:svn).with('--non-interactive', '--trust-server-cert', 'checkout',
- resource.value(:source), resource.value(:path))
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', '--trust-server-cert', 'checkout',
+ resource.value(:source), resource.value(:path))
provider.create
end
end
context 'with specific include paths' do
it 'raises an error when trying to make a repo' do
resource[:includes] = test_paths
expect { provider.create }.to raise_error(Puppet::Error, %r{Specifying include paths on a nonexistent repo.})
end
it 'performs a sparse checkout' do
resource[:source] = 'exists'
resource[:includes] = test_paths
expect(Dir).to receive(:chdir).with('/tmp/vcsrepo').once.and_yield
- expect(provider).to receive(:svn).with('--non-interactive', 'checkout', '--depth', 'empty',
- resource.value(:source),
- resource.value(:path))
- expect(provider).to receive(:svn).with('--non-interactive', 'update', '--depth', 'empty',
- *test_paths_parents)
- expect(provider).to receive(:svn).with('--non-interactive', 'update',
- *resource[:includes])
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'checkout', '--depth', 'empty',
+ resource.value(:source),
+ resource.value(:path))
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'update', '--depth', 'empty',
+ *test_paths_parents)
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'update',
+ *resource[:includes])
provider.create
end
it 'performs a sparse checkout at a specific revision' do
resource[:source] = 'exists'
resource[:revision] = 1
resource[:includes] = test_paths
expect(Dir).to receive(:chdir).with('/tmp/vcsrepo').once.and_yield
- expect(provider).to receive(:svn).with('--non-interactive', 'checkout', '-r',
- resource.value(:revision),
- '--depth', 'empty',
- resource.value(:source),
- resource.value(:path))
- expect(provider).to receive(:svn).with('--non-interactive', 'update',
- '--depth', 'empty',
- '-r', resource.value(:revision),
- *test_paths_parents)
- expect(provider).to receive(:svn).with('--non-interactive', 'update', '-r',
- resource.value(:revision),
- *resource[:includes])
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'checkout', '-r',
+ resource.value(:revision),
+ '--depth', 'empty',
+ resource.value(:source),
+ resource.value(:path))
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'update',
+ '--depth', 'empty',
+ '-r', resource.value(:revision),
+ *test_paths_parents)
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'update', '-r',
+ resource.value(:revision),
+ *resource[:includes])
provider.create
end
it 'performs a sparse checkout with a specific depth' do
resource[:source] = 'exists'
resource[:depth] = 'files'
resource[:includes] = test_paths
expect(Dir).to receive(:chdir).with('/tmp/vcsrepo').once.and_yield
- expect(provider).to receive(:svn).with('--non-interactive', 'checkout', '--depth', 'empty',
- resource.value(:source),
- resource.value(:path))
- expect(provider).to receive(:svn).with('--non-interactive', 'update',
- '--depth', 'empty',
- *test_paths_parents)
- expect(provider).to receive(:svn).with('--non-interactive', 'update',
- '--depth', resource.value(:depth),
- *resource[:includes])
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'checkout', '--depth', 'empty',
+ resource.value(:source),
+ resource.value(:path))
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'update',
+ '--depth', 'empty',
+ *test_paths_parents)
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'update',
+ '--depth', resource.value(:depth),
+ *resource[:includes])
provider.create
end
it 'performs a sparse checkout at a specific depth and revision' do
resource[:source] = 'exists'
resource[:revision] = 1
resource[:depth] = 'files'
resource[:includes] = test_paths
expect(Dir).to receive(:chdir).with('/tmp/vcsrepo').once.and_yield
- expect(provider).to receive(:svn).with('--non-interactive', 'checkout', '-r',
- resource.value(:revision),
- '--depth', 'empty',
- resource.value(:source),
- resource.value(:path))
- expect(provider).to receive(:svn).with('--non-interactive', 'update',
- '--depth', 'empty',
- '-r', resource.value(:revision),
- *test_paths_parents)
- expect(provider).to receive(:svn).with('--non-interactive', 'update',
- '-r', resource.value(:revision),
- '--depth', resource.value(:depth),
- *resource[:includes])
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'checkout', '-r',
+ resource.value(:revision),
+ '--depth', 'empty',
+ resource.value(:source),
+ resource.value(:path))
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'update',
+ '--depth', 'empty',
+ '-r', resource.value(:revision),
+ *test_paths_parents)
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'update',
+ '-r', resource.value(:revision),
+ '--depth', resource.value(:depth),
+ *resource[:includes])
provider.create
end
end
end
describe 'destroying' do
it 'removes the directory' do
expect_rm_rf
provider.destroy
end
end
describe 'checking existence' do
it "runs `svn info` on the path when there's a source" do
resource[:source] = 'dummy'
expect_directory?(true, resource.value(:path))
- expect(provider).to receive(:svn).with('info', resource[:path])
+ expect(provider).to receive(:svn_wrapper).with('info', resource[:path])
provider.exists?
end
it "runs `svnlook uuid` on the path when there's no source" do
expect_directory?(true, resource.value(:path))
expect(provider).to receive(:svnlook).with('uuid', resource[:path])
provider.exists?
end
end
describe 'checking the revision property' do
before(:each) do
- allow(provider).to receive(:svn).with('--non-interactive', 'info').and_return(fixture(:svn_info))
+ allow(provider).to receive(:svn_wrapper).with('--non-interactive', 'info').and_return(fixture(:svn_info))
end
it "uses 'svn info'" do
expect_chdir
expect(provider.revision).to eq('4') # From 'Revision', not 'Last Changed Rev'
end
end
describe 'setting the revision property' do
let(:revision) { '30' }
context 'with conflict' do
it "uses 'svn update'" do
resource[:conflict] = 'theirs-full'
expect_chdir
- expect(provider).to receive(:svn).with('--non-interactive', 'update',
- '-r', revision, '--accept', resource.value(:conflict))
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'update',
+ '-r', revision, '--accept', resource.value(:conflict))
provider.revision = revision
end
end
context 'without conflict' do
it "uses 'svn update'" do
expect_chdir
- expect(provider).to receive(:svn).with('--non-interactive', 'update', '-r', revision)
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'update', '-r', revision)
provider.revision = revision
end
end
end
describe 'setting the revision property and repo source' do
let(:revision) { '30' }
context 'with conflict' do
it "uses 'svn switch'" do
resource[:source] = 'an-unimportant-value'
resource[:conflict] = 'theirs-full'
expect_chdir
- expect(provider).to receive(:svn).with('--non-interactive', 'switch', '-r', revision, 'an-unimportant-value', '--accept', resource.value(:conflict))
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'switch', '-r', revision, 'an-unimportant-value', '--accept', resource.value(:conflict))
provider.revision = revision
end
end
context 'without conflict' do
it "uses 'svn switch' - variation one" do
resource[:source] = 'an-unimportant-value'
expect_chdir
- expect(provider).to receive(:svn).with('--non-interactive', 'switch', '-r', revision, 'an-unimportant-value')
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'switch', '-r', revision, 'an-unimportant-value')
provider.revision = revision
end
it "uses 'svn switch' - variation two" do
resource[:source] = 'an-unimportant-value'
resource[:revision] = '30'
expect_chdir
- expect(provider).to receive(:svn).with('--non-interactive', 'switch', '-r', resource.value(:revision), 'an-unimportant-value')
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'switch', '-r', resource.value(:revision), 'an-unimportant-value')
provider.source = resource.value(:source)
end
end
end
describe 'checking the source property' do
before(:each) do
- allow(provider).to receive(:svn).with('--non-interactive', 'info').and_return(fixture(:svn_info))
+ allow(provider).to receive(:svn_wrapper).with('--non-interactive', 'info').and_return(fixture(:svn_info))
end
it "uses 'svn info'" do
expect_chdir
expect(provider.source).to eq('http://example.com/svn/trunk') # From URL
end
end
describe 'checking the basic_auth properties' do
context 'when basic_auth_username is set and basic_auth_password is not set' do
it 'fails' do
resource[:source] = 'an-unimportant-value'
resource[:basic_auth_username] = 'dummy_user'
expect { provider.create }.to raise_error RuntimeError, %r{you must specify the HTTP basic authentication password.+}i
end
end
context 'when basic_auth_username is not set and basic_auth_password is set' do
it 'fails' do
resource[:source] = 'an-unimportant-value'
resource[:basic_auth_password] = 'dummy_pass'
expect { provider.create }.to raise_error RuntimeError, %r{you must specify the HTTP .+username.*}i
end
end
+ context 'when basic_auth_password is Sensitive' do
+ let(:resource) do
+ Puppet::Type.type(:vcsrepo).new(name: 'test',
+ ensure: :present,
+ provider: :svn,
+ path: '/tmp/vcsrepo',
+ source: 'an-unimportant-value',
+ sensitive_parameters: [:basic_auth_password],
+ basic_auth_username: 'dummy_user',
+ basic_auth_password: Puppet::Pops::Types::PSensitiveType::Sensitive.new('dummy_pass'))
+ end
+
+ it 'works' do
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', '--username', resource.value(:basic_auth_username),
+ '--password', resource.value(:basic_auth_password).unwrap, '--no-auth-cache',
+ 'checkout', resource.value(:source), resource.value(:path))
+ provider.create
+ end
+ end
end
describe 'setting the source property' do
context 'with conflict' do
it "uses 'svn switch'" do
resource[:source] = 'http://example.com/svn/tags/1.0'
resource[:conflict] = 'theirs-full'
expect_chdir
- expect(provider).to receive(:svn).with('--non-interactive', 'switch', '--accept', resource.value(:conflict), resource.value(:source))
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'switch', '--accept', resource.value(:conflict), resource.value(:source))
provider.source = resource.value(:source)
end
end
context 'without conflict' do
it "uses 'svn switch'" do
resource[:source] = 'http://example.com/svn/tags/1.0'
expect_chdir
- expect(provider).to receive(:svn).with('--non-interactive', 'switch',
- resource.value(:source))
+ expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'switch',
+ resource.value(:source))
provider.source = resource.value(:source)
end
end
end
end