Version Control Bodies and Bundles
The vcs.cf
library provides bundles for working with version control tools.
To use these bodies, add the following to your policy:
body file control
{
inputs => { "vcs.cf" }
}
agent bundles
git_init
Prototype: git_init(repo_path)
Description: initializes a new git repository if it does not already exist
Arguments:
repo_path
: absolute path of where to initialize a git repository
Example:
bundle agent my_git_repositories
{
vars:
"basedir" string => "/var/git";
"repos" slist => { "myrepo", "myproject", "myPlugForMoreHaskell" };
files:
"$(basedir)/$(repos)/."
create => "true";
methods:
"git_init" usebundle => git_init("$(basedir)/$(repos)");
}
Implementation:
bundle agent git_init(repo_path)
{
classes:
"ok_norepo" not => fileexists("$(repo_path)/.git");
methods:
ok_norepo::
"git_init" usebundle => git("$(repo_path)", "init", "");
}
git_add
Prototype: git_add(repo_path, file)
Description: adds files to the supplied repository's index
Arguments:
repo_path
: absolute path to a git repositoryfile
: a file to stage in the index
Example:
bundle agent add_files_to_git_index
{
vars:
"repo" string => "/var/git/myrepo";
"files" slist => { "fileA", "fileB", "fileC" };
methods:
"git_add" usebundle => git_add("$(repo)", "$(files)");
}
Implementation:
bundle agent git_add(repo_path, file)
{
classes:
"ok_repo" expression => fileexists("$(repo_path)/.git");
methods:
ok_repo::
"git_add" usebundle => git("$(repo_path)", "add", "$(file)");
}
git_checkout
Prototype: git_checkout(repo_path, branch)
Description: checks out an existing branch in the supplied git repository
Arguments:
repo_path
: absolute path to a git repositorybranch
: the name of an existing git branch to checkout
Example:
bundle agent git_checkout_some_existing_branch
{
vars:
"repo" string => "/var/git/myrepo";
"branch" string => "dev/some-topic-branch";
methods:
"git_checkout" usebundle => git_checkout("$(repo)", "$(branch)");
}
Implementation:
bundle agent git_checkout(repo_path, branch)
{
classes:
"ok_repo" expression => fileexists("$(repo_path)/.git");
methods:
ok_repo::
"git_checkout" usebundle => git("$(repo_path)", "checkout", "$(branch)");
}
git_checkout_new_branch
Prototype: git_checkout_new_branch(repo_path, new_branch)
Description: checks out and creates a new branch in the supplied git repository
Arguments:
repo_path
: absolute path to a git repositorynew_branch
: the name of the git branch to create and checkout
Example:
bundle agent git_checkout_new_branches
{
vars:
"repo[myrepo]" string => "/var/git/myrepo";
"branch[myrepo]" string => "dev/some-new-topic-branch";
"repo[myproject]" string => "/var/git/myproject";
"branch[myproject]" string => "dev/another-new-topic-branch";
"repo_names" slist => getindices("repo");
methods:
"git_checkout_new_branch" usebundle => git_checkout_new_branch("$(repo[$(repo_names)])", "$(branch[$(repo_names)])");
}
Implementation:
bundle agent git_checkout_new_branch(repo_path, new_branch)
{
classes:
"ok_repo" expression => fileexists("$(repo_path)/.git");
methods:
ok_repo::
"git_checkout" usebundle => git("$(repo_path)", "checkout -b", "$(branch)");
}
git_clean
Prototype: git_clean(repo_path)
Description: Ensure that a given git repo is clean
Arguments:
repo_path
: Path to the clone
Example:
methods:
"test"
usebundle => git_clean("/opt/cfengine/masterfiles_staging_tmp"),
comment => "Ensure that the staging area is a clean clone";
Implementation:
bundle agent git_clean(repo_path)
{
methods:
"" usebundle => git("$(repo_path)", "clean", ' --force -d'),
comment => "To have a clean clone we must remove any untracked files and
directories. These should have all been stashed, but in case
of error we go ahead and clean anyway.";
}
git_stash
Prototype: git_stash(repo_path, stash_name)
Description: Stash any changes (including untracked files) in repo_path
Arguments:
repo_path
: Path to the clonestash_name
: Stash name
Example:
methods:
"test"
usebundle => git_stash("/opt/cfengine/masterfiles_staging_tmp", "temp"),
comment => "Stash any changes, including untracked files";
Implementation:
bundle agent git_stash(repo_path, stash_name)
{
methods:
"" usebundle => git($(repo_path), "stash", 'save --quiet --include-untracked "$(stash_name)"'),
comment => "So that we don't lose any trail of what happend and so that
we don't accidentally delete something important we stash any
changes.
Note:
1. This promise will fail if user.email is not set
2. We are respecting ignored files.";
}
git_stash_and_clean
Prototype: git_stash_and_clean(repo_path)
Description: Ensure that a given git repo is clean and attempt to save any modifications
Arguments:
repo_path
: Path to the clone
Example:
methods:
"test"
usebundle => git_stash_and_clean("/opt/cfengine/masterfiles_staging_tmp"),
comment => "Ensure that the staging area is a clean clone after attempting to stash any changes";
Implementation:
bundle agent git_stash_and_clean(repo_path)
{
vars:
"stash" string => "CFEngine AUTOSTASH: $(sys.date)";
methods:
"" usebundle => git_stash($(repo_path), $(stash)),
classes => scoped_classes_generic("bundle", "git_stash");
git_stash_ok::
"" usebundle => git_clean($(repo_path));
reports:
git_stash_not_ok::
"$(this.bundle):: Warning: Not saving changes or cleaning. Git stash failed. Perhaps 'user.email' or 'user.name' is not set.";
}
git_commit
Prototype: git_commit(repo_path, message)
Description: executes a commit to the specificed git repository
Arguments:
repo_path
: absolute path to a git repositorymessage
: the message to associate to the commmit
Example:
bundle agent make_git_commit
{
vars:
"repo" string => "/var/git/myrepo";
"msg" string => "dituri added some bundles for common git operations";
methods:
"git_commit" usebundle => git_commit("$(repo)", "$(msg)");
}
Implementation:
bundle agent git_commit(repo_path, message)
{
classes:
"ok_repo" expression => fileexists("$(repo_path)/.git");
methods:
ok_repo::
"git_commit" usebundle => git("$(repo_path)", "commit", '-m "$(message)"');
}
git
Prototype: git(repo_path, subcmd, args)
Description: generic interface to git
Arguments:
repo_path
: absolute path to a new or existing git repositorysubcmd
: any valid git sub-commandargs
: a single string of arguments to pass
This bundle will drop privileges if running as root (uid 0) and the
repository is owned by a different user. Use DEBUG
or DEBUG_git
(from the
command line, -D DEBUG_git
) to see every Git command it runs.
Example:
bundle agent git_rm_files_from_staging
{
vars:
"repo" string => "/var/git/myrepo";
"git_cmd" string => "reset --soft";
"files" slist => { "fileA", "fileB", "fileC" };
methods:
"git_reset" usebundle => git("$(repo)", "$(git_cmd)", "HEAD -- $(files)");
}
Implementation:
bundle agent git(repo_path, subcmd, args)
{
vars:
"oneliner" string => "$(paths.path[git])";
"repo_uid"
string => filestat($(repo_path), "uid"),
comment => "So that we don't mess up permissions, we will just execute
all commands as the current owner of .git";
"repo_gid"
string => filestat($(repo_path), "gid"),
comment => "So that we don't mess up permissions, we will just execute
all commands as the current group of .git";
# We get the passwd entry from the user that owns the repo so
# that we can extract the home directory for later use.
"repo_uid_passwd_ent"
string => execresult("$(paths.getent) passwd $(repo_uid)", noshell),
comment => "We need to extract the home directory of the repo
owner so that it can be used to avoid errors from
unprivledged execution trying to access the root
users git config.";
classes:
"am_root" expression => strcmp($(this.promiser_uid), "0");
# $(repo_uid) must be defined before we try to test this or we will end up
# having at least one pass during evaluation the agent will not know it
# needs to drop privileges, leading to some files like .git/index being
# created with elevated privileges, and subsequently causing the agent to
# not be able to commit as a normal user.
"need_to_drop"
not => strcmp($(this.promiser_uid), $(repo_uid)),
ifvarclass => isvariable( repo_uid );
am_root.need_to_drop::
# This regular expression could be tightened up
# Extract the home directory from the owner of the repository
# into $(repo_uid_passwd[1])
"extracted_repo_uid_home"
expression => regextract( ".*:.*:\d+:\d+:.*:(.*):.*",
$(repo_uid_passwd_ent),
"repo_uid_passwd" ),
ifvarclass => isvariable("repo_uid_passwd_ent");
commands:
am_root.need_to_drop::
# Because cfengine does not inherit the shell environment when
# executing commands, git will look for the root users git
# config and error when the executing user does not have
# access. So we need to set the home directory of the executing
# user.
"$(paths.env) HOME=$(repo_uid_passwd[1]) $(oneliner)"
args => "$(subcmd) $(args)",
classes => kept_successful_command,
contain => setuidgid_dir( $(repo_uid), $(repo_gid), $(repo_path) );
!am_root|!need_to_drop::
"$(oneliner)"
args => "$(subcmd) $(args)",
classes => kept_successful_command,
contain => in_dir( $(repo_path) );
reports:
"DEBUG|DEBUG_$(this.bundle).am_root.need_to_drop"::
"DEBUG $(this.bundle): with dropped privileges to uid '$(repo_uid)' and gid '$(repo_gid)', in directory '$(repo_path)', running Git command '$(paths.env) HOME=\"$(repo_uid_passwd[1])\" $(oneliner) $(subcmd) $(args)'"
ifvarclass => isvariable("repo_uid_passwd[1]");
"DEBUG|DEBUG_$(this.bundle).(!am_root|!need_to_drop)"::
"DEBUG $(this.bundle): with current privileges, in directory '$(repo_path)', running Git command '$(oneliner) $(subcmd) $(args)'";
}