diff options
author | Daniel Silverstone <dsilvers@digital-scurf.org> | 2017-06-18 19:53:40 +0100 |
---|---|---|
committer | Daniel Silverstone <dsilvers@digital-scurf.org> | 2017-08-02 16:57:11 -0400 |
commit | 1476f24a336f1e53c70482e989a8d7641bcd212d (patch) | |
tree | 40dbd7585dec833936a01a85f25857d393e0f3e9 | |
parent | a495a8b4af8715ffac1a8c1619256b7bc47d7c3c (diff) | |
download | gitano-1476f24a336f1e53c70482e989a8d7641bcd212d.tar.gz |
Support git_multimail.py as a plugindsilvers/multimail
This plugin supports running git_multimail.py during the
post-receive hook. It uses Lace and Supple to provide simple
but flexible control of the service, and requires sysadmins to
pre-configure the site outside of gitano-admin control which
should ensure a reduction in the abuse-ability of it.
-rw-r--r-- | plugins/git-multimail.lua | 218 |
1 files changed, 218 insertions, 0 deletions
diff --git a/plugins/git-multimail.lua b/plugins/git-multimail.lua new file mode 100644 index 0000000..23100b6 --- /dev/null +++ b/plugins/git-multimail.lua @@ -0,0 +1,218 @@ +-- Git Multimail Plugin +-- +-- This plugin enables support for git-multimail on the server-side which +-- allows for the nice(r) reporting of changes by email. +-- +-- Copyright 2017 Daniel Silverstone <dsilvers@digital-scurf.org> +-- All rights reserved. +-- +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions +-- are met: +-- 1. Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- 2. Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- 3. Neither the name of the author nor the names of their contributors +-- may be used to endorse or promote products derived from this software +-- without specific prior written permission. +-- +-- THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +-- ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +-- SUCH DAMAGE. + +local gitano = require "gitano" + +local luxio = require "luxio" +local sp = require "luxio.subprocess" + +local git_multimail_py = nil + +do + local args = { + "git", "config", "--path", "gitano.multimail.location", + stdout = sp.PIPE, + cwd = luxio.getenv("HOME"), + } + local proc = sp.spawn_simple(args) + git_multimail_py = proc.stdout:read("*l") + local how, why = proc:wait() + if how ~= "exit" then + return -- don't register, something really went wrong + end + if why ~= 0 then + git_multimail_py = nil + end +end + +local GLOBAL_CONFS = { + environment = "generic", + refFilterExclusionRegex = "^refs/gitano/", + verbose = "true", +} + +local function run_multimail(confbits, env, stdin) + local args = { + env = env, + stdin = sp.PIPE, + stdout = sp.PIPE, + stderr = sp.PIPE, + git_multimail_py, "--stdout" + } + for k, v in pairs(confbits) do + args[#args+1] = "-c" + args[#args+1] = "multimailhook." .. k .. "=" .. v + end + gitano.log.state("About to spawn git_multimail.py as...") + for _, v in ipairs(args) do + gitano.log.state("=> " .. v) + end + local proc = sp.spawn_simple(args) + proc.stdin:write(stdin) + proc.stdin:close() + local outcontent = proc.stdout:read("*a") + local errcontent = proc.stderr:read("*a") + local how, why = proc:wait() + local function show(what, pfx) + if what == "" then return end + if what:sub(-1) ~= "\n" then what = what .. "\n" end + for line in what:gfind("([^\n]*)\n") do + gitano.log.state(pfx .. ": " .. line) + end + end + gitano.log.state("Git Multimail says:") + show(outcontent, "O") + show(errcontent, "E") + if (how ~= "exit") or (why ~= 0) then + gitano.log.error("git_multimail.py failed: " .. tostring(how) .. " " .. tostring(why)) + end + gitano.log.state("Done with multimail") +end + +local function git_multimail_post_receive_hook(repo, updates) + -- We don't get here at all if we don't have git_multimail.py's location + -- as such, our first job is to request authorisation from the ACL + local context = { + user = "gitano/multimail", + source = "plugin_multimail", + operation = "multimail", + } + gitano.log.ddebug("Multimail: Asking " .. repo.name .. " if multimail is allowed...") + local action, reason = repo:run_lace(context) + gitano.log.ddebug(("Multimail: action=%q reason=%q"):format(action,reason)) + if action ~= "allow" then + gitano.log.info("Multimail disallowed: " .. tostring(action) .. ": " .. tostring(reason)) + return "continue" + end + + -- Okay, we're allowed to run, so let's build our config... + local confbits = gitano.util.deep_copy(GLOBAL_CONFS) + local conf = repo.config + local username = luxio.getenv("GITANO_USER") + local user = conf.users[username] or { + real_name = "Anonymous", + email_address = "postmaster@localhost.localdomain" + } + -- And we need to set 'from' + confbits.from = user.real_name .. " <" .. user.email_address .. ">" + + local function layer_confbits(fromrepo, pfx) + gitano.log.ddebug("Multimail: Attempting to layer from " .. tostring(fromrepo.name) .. " via prefix " .. pfx) + -- Note this is currently an unstable API + local from = fromrepo.project_config + for key, value in from:each(pfx) do + key = key:sub(#pfx+2,-1) + gitano.log.ddebug(("Multimail: => setting %q to %q"):format(key,value)) + confbits[key] = tostring(value) + end + end + local gitano_admin_repo = ( + gitano.repository.find(repo.config, "gitano-admin.git")) + layer_confbits(gitano_admin_repo, "defaults.multimail") + layer_confbits(repo, "multimail") + layer_confbits(gitano_admin_repo, "override.multimail") + + -- Now we run a supple hook (if necessary) which will be able to adjust + -- the config bits... + if repo:uses_hook("multimail") then + gitano.log.debug("Multimail: Configuring supple for hook") + gitano.actions.set_supple_globals("multimail") + gitano.log.info("Multimail: Running hook") + local info = { + username = username, + source = "plugin_multimail", + realname = user.real_name or "", + email = user.email_address or "", + } + local ok, msg = gitano.supple.run_hook("multimail", repo, info, updates, confbits) + if not ok then + gitano.log.error("Multimail: Hook failed: " .. tostring(msg)) + gitano.log.error("Multimail: Aborting send") + return + end + gitano.log.info("Multimail: Finished running hook, continuing...") + end + + -- Next we clean up the config bits, by copying over only what we whitelist + local unfiltered = confbits + confbits = {} + for _, key in ipairs( + { + "mailingList", "refchangeList", "announceList", "commitList", + "from", + "announceShortLog", "commitBrowseURL", "refchangeShowGraph", + "refchangeShowLog", "administrator", "emailPrefix", + "refFilterInclusionRegex", "refFilterExclusionRegex", + "refFilterDoSendRegex", "refFilterDontSendRegex", + "verbose", "stdout", + }) do + local val = unfiltered[key] + if val and val ~= "" then + gitano.log.ddebug( + ("Multimail: Permitted %q => %q"):format(key, unfiltered[key])) + confbits[key] = unfiltered[key] + end + end + + -- No matter what goes on in the clod configs, we need to set repoName + confbits.repoName = repo.name + + local fullusername = user.real_name .. " (" .. username .. ")" + local env = { + USERNAME = fullusername, + USER = fullusername, + } + + -- Prepare the stdin for git_multimail.py, in post-receive format + local stdin = {} + for ref, chg in pairs(updates) do + stdin[#stdin+1] = table.concat({chg.oldsha, chg.newsha, ref}, " ") + end + stdin = table.concat(stdin, "\n") .. "\n" + + -- Finally, if there's any address set at all, run multimail + if (confbits.mailingList or + confbits.announceList or + confbits.commitList or + confbits.refchangeList) then + run_multimail(confbits, env, stdin) + else + gitano.log.info("Multimail: Did not run, no target addresses set") + end + + return "continue" +end + +if git_multimail_py ~= nil and luxio.getenv("GITANO_USER") ~= "gitano/bypass" then + gitano.hooks.add(gitano.hooks.names.POST_RECEIVE, + 100, git_multimail_post_receive_hook) +end |