summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Silverstone <dsilvers@digital-scurf.org>2017-06-18 19:53:40 +0100
committerDaniel Silverstone <dsilvers@digital-scurf.org>2017-08-02 16:57:11 -0400
commit1476f24a336f1e53c70482e989a8d7641bcd212d (patch)
tree40dbd7585dec833936a01a85f25857d393e0f3e9
parenta495a8b4af8715ffac1a8c1619256b7bc47d7c3c (diff)
downloadgitano-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.lua218
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