Version Control Bodies and Bundles

Table of Contents

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 repository
  • file: 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 repository
  • branch: 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 repository
  • new_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 clone
  • stash_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 repository
  • message: 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 repository
  • subcmd: any valid git sub-command
  • args: 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 inform_mode (from the command line, -I) 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";

  classes:
      "am_root" expression => strcmp($(this.promiser_uid), "0");
      "need_to_drop" not => strcmp($(this.promiser_uid), $(repo_uid));

  commands:
    am_root.need_to_drop::
      "$(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:
    inform_mode.am_root.need_to_drop::
      "$(this.bundle): with dropped privileges to uid '$(repo_uid)' and gid '$(repo_gid)', in directory '$(repo_path)', running Git command '$(oneliner) $(subcmd) $(args)'";

    inform_mode.(!am_root|!need_to_drop)::
      "$(this.bundle): with current privileges, in directory '$(repo_path)', running Git command '$(oneliner) $(subcmd) $(args)'";
}