From 17f73b454311ef232c29e85d9eacde7941dd8097 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Sat, 8 Apr 2017 15:25:53 +0100 Subject: Add section on git hooks --- doc/admin/000.mdwn | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) (limited to 'doc') diff --git a/doc/admin/000.mdwn b/doc/admin/000.mdwn index 1de5ca4..c80e4cd 100644 --- a/doc/admin/000.mdwn +++ b/doc/admin/000.mdwn @@ -433,6 +433,142 @@ so you can split up your rules: # Branches must not be tags deny "Branches may only be commits" [ref prefix refs/heads/] [newtype exact commit] +# Git Hook scripts + +Gitano administrators may create, or delegate creation of +[Lua][lua] scripts to take action or provide further authentication +when content is pushed to a repository. + +These [Lua][lua] scripts are sandboxed using [Supple][supple], +to make it safer to run user-defined code. + +## How to add a hook + +Hooks are stored in the `gitano-admin.git` repository +and the `refs/gitano/admin` branch of each other repository, +so that there can be traceability of who is responsible for each hook. + +Global hooks in `gitano-admin.git` are loaded +from the `global-hooks` subdirectory. +Per-repository hooks in the `refs/gitano/admin` branch +are loaded from the `hooks` subdirectory. + +In both cases hooks are loaded from a file named after the hook +as defined by [githooks(5)][] suffixed with `.lua`. + +Currently the only hooks that are supported are: + +1. pre-receive +2. update +3. post-receive + +So a global pre-receive hook would be in the file `global-hooks/pre-receive.lua` +and a per-repository post-receive hook would be in `hooks/post-receive.lua`. + +## APIs available to hooks + +1. `log.$level(...)` to emit text at that log level. + $level can be one of: + + 1. `state` + 2. `crit` or `critical` + 3. `err` or `error` + 4. `warn` or `warning` + 5. `chat` + 6. `info` + 7. `debug` + 8. `ddebug` or `deepdebug` + +2. `fetch(url, headers, body, content_type)` during post-receive hooks. + +3. A `repo` object as the first parameter to per-repository hooks, + with the following methods: + + ------------------------------------ ---------------------------------------------------------------------------- + `:get(sha1ish)` Get the contents of a git object from anything matching [gitrevisions(7)][]. + `:get_config(confname)` Get the value confname from the repo config if it is a single value + `:get_config_list(confname)` Get the value confname from the repo config if it is a list value + `:check_signature(obj, keyringname)` Check whether `obj` is an object signed by a key in keyring `keyringname`. + ------------------------------------ ---------------------------------------------------------------------------- + + Objects returned from `get` are [gall][] objects. + Refer to [gall's API documentation][gall-api] for details. + +4. An `actor` table in the global environment, + containing the following indices: + + -------- -------------------------------------------------------------- + username Gitano username of user doing the push. + keytag Name of the ssh key the user is pushing with. + source "ssh", "http" or "git" depending on the protocol used to push. + realname Real name of user from user configuration. + email E-Mail of user from user configuration. + -------- -------------------------------------------------------------- + +5. The `refname`, `oldsha` and `newsha` as the third to fifth parameters + during per-repository update hooks, as described in [githooks(5)][]. + + Checking whether the master ref is deleted would be accomplished by: + + local _, refname, oldsha, newsha = ... + if refname == "refs/heads/master" and newsha == ("0"):rep(40) then + log.state("Master deleted") + end + +6. For pre-receive or post-receive per-repository hooks as the third parameter, + a table indexed by the `refnames` being modified + containing tables of the `oldsha` and `newsha` indexed + either at positional indices `1` and `2`, + or by name `oldsha` and `newsha`. + Checking whether the master ref is deleted would be accomplished by: + + local _, updates = ... + if (updates["refs/heads/master"] or {}).newsha == ("0"):rep(40) then + log.state("Master deleted") + end + +7. Global hooks work the same as per-repository hooks, + except their first parameter is instead a callable function + which will run the per-repository hook when called, + so global hooks may either replace or augment per-repository hooks. + + To unconditionally run some global hook code and then call a local hook + your global hook would be structured something like: + + -- Example global-hooks/update.lua + local hookf, repo, refname, oldsha, newsha = ... + -- Do stuff with repo/refname/oldsha/newsha here + -- + -- And finally (optionally) tail-chain through to the per-repo hook + return hookf(repo, refname, oldsha, newsha) + + To unconditionally ignore update hooks and log why: + + -- Example global-hooks/update.lua + local hookf, repo, refname, oldsha, newsha = ... + local treeish = repo:get("refs/gitano/admin").sha + if repo:get(treeish..":hooks/update.lua") then + log.state("Update hooks are disabled") + end + + To provide a default if a hook is not defined: + + -- Example global-hooks/update.lua + local hookf, repo, refname, oldsha, newsha = ... + local treeish = repo:get("refs/gitano/admin").sha + if repo:get(treeish..":hooks/update.lua") then + return hookf(repo, refname, oldsha, newsha) + end + -- Do stuff with repo/refname/oldsha/newsha here + + +[lua]: https://www.lua.org/ +[supple]: https://www.gitano.org.uk/supple/ +[githooks(5)]: https://git-scm.com/docs/githooks +[gitrevisions(7)]: https://git-scm.com/docs/gitrevisions +[gall]: https://www.gitano.org.uk/gall/ +[gall-api]: file:///usr/share/doc/lua-gall-doc/html/index.html + # Backup and restore of a Gitano instance When gitano-setup is run, the admin needs to specify a Unix user in -- cgit v1.2.1