summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile14
-rw-r--r--bin/gitano-post-receive-hook.in130
-rw-r--r--lang/en.lua3
-rw-r--r--lib/gitano.lua7
-rw-r--r--lib/gitano/auth.lua18
-rw-r--r--lib/gitano/hooks.lua137
-rw-r--r--plugins/testing-hooks.lua76
-rw-r--r--testing/01-hooks.yarn73
-rw-r--r--testing/gitano-test-tool.in34
-rw-r--r--testing/library.yarn6
10 files changed, 429 insertions, 69 deletions
diff --git a/Makefile b/Makefile
index b5e3296..44d7db0 100644
--- a/Makefile
+++ b/Makefile
@@ -32,13 +32,13 @@ BINS := gitano-setup
TEST_BIN_NAMES := gitano-test-tool
-TESTS := 01-basics 02-commands-as 02-commands-config 02-commands-copy \
- 02-commands-count-objects 02-commands-create 02-commands-destroy \
- 02-commands-fsck 02-commands-gc 02-commands-graveyard \
- 02-commands-git-upload-archive 02-commands-group 02-commands-help \
- 02-commands-keyring \
- 02-commands-ls 02-commands-rename 02-commands-rsync \
- 02-commands-sshkey 02-commands-user 02-commands-whoami 03-cgit-support \
+TESTS := 01-basics 01-hooks 02-commands-as 02-commands-config 02-commands-copy \
+ 02-commands-count-objects 02-commands-create 02-commands-destroy \
+ 02-commands-fsck 02-commands-gc 02-commands-graveyard \
+ 02-commands-git-upload-archive 02-commands-group 02-commands-help \
+ 02-commands-keyring \
+ 02-commands-ls 02-commands-rename 02-commands-rsync \
+ 02-commands-sshkey 02-commands-user 02-commands-whoami 03-cgit-support \
03-shallow-push 03-treedelta-rules 03-force-pushing
diff --git a/bin/gitano-post-receive-hook.in b/bin/gitano-post-receive-hook.in
index ad77da2..cb77e06 100644
--- a/bin/gitano-post-receive-hook.in
+++ b/bin/gitano-post-receive-hook.in
@@ -141,71 +141,83 @@ local function report_repo(reponame, repo, msg)
end
end
-if repo.name == "gitano-admin" and updates[admin_repo.HEAD] then
- -- Updating the 'master' of gitano-admin, let's iterate all the repositories
-
- gitano.log.syslog.info("Updating gitano-admin")
-
- local msg = gitano.i18n.expand("SCANNING_FOR_UPDATES")
- gitano.log.chat(msg)
- gitano.log.syslog.info(msg)
-
- local ok, msg = gitano.repository.foreach(config, report_repo)
- if not ok then
- gitano.log.crit(msg)
+function post_receive_core_handler(repo, updates)
+ if repo.name == "gitano-admin" and updates[admin_repo.HEAD] then
+ -- Updating the 'master' of gitano-admin, let's iterate all the repositories
+
+ gitano.log.syslog.info("Updating gitano-admin")
+
+ local msg = gitano.i18n.expand("SCANNING_FOR_UPDATES")
+ gitano.log.chat(msg)
+ gitano.log.syslog.info(msg)
+
+ local ok, msg = gitano.repository.foreach(config, report_repo)
+ if not ok then
+ gitano.log.crit(msg)
+ end
+
+ msg = gitano.i18n.expand("ALL_UPDATES_DONE")
+ gitano.log.chat(msg)
+ gitano.log.syslog.info(msg)
+
+ local proc = sp.spawn({
+ gitano.config.lib_bin_path() .. "/gitano-update-ssh",
+ gitano.config.repo_path()
+ })
+ local how, why = proc:wait()
+ if how ~= "exit" or why ~= 0 then
+ gitano.log.crit(gitano.i18n.expand("ERROR_UPDATE_SSH_NOT_WORK"))
+ end
+ elseif repo.name ~= "gitano-admin" then
+ -- Not gitano-admin at all, so run the update-server-info stuff
+ gitano.log.info(gitano.i18n.expand("UPDATE_HTTP_INFO"))
+ local ok, err = repo.git:update_server_info()
+ if not ok then
+ gitano.log.warn(err)
+ end
+ gitano.log.info(gitano.i18n.expand("UPDATE_LASTMOD_DATE"))
+ local shas = {}
+ for _, t in pairs(updates) do
+ shas[#shas+1] = t.newsha
+ end
+ local ok, err = repo:update_modified_date(shas)
+ if not ok then
+ gitano.log.warn(err)
+ end
end
+ return "continue"
+end
- msg = gitano.i18n.expand("ALL_UPDATES_DONE")
- gitano.log.chat(msg)
- gitano.log.syslog.info(msg)
-
- local proc = sp.spawn({
- gitano.config.lib_bin_path() .. "/gitano-update-ssh",
- gitano.config.repo_path()
- })
- local how, why = proc:wait()
- if how ~= "exit" or why ~= 0 then
- gitano.log.crit(gitano.i18n.expand("ERROR_UPDATE_SSH_NOT_WORK"))
- end
-elseif repo.name ~= "gitano-admin" then
- -- Not gitano-admin at all, so run the update-server-info stuff
- gitano.log.info(gitano.i18n.expand("UPDATE_HTTP_INFO"))
- local ok, err = repo.git:update_server_info()
- if not ok then
- gitano.log.warn(err)
- end
- gitano.log.info(gitano.i18n.expand("UPDATE_LASTMOD_DATE"))
- local shas = {}
- for _, t in pairs(updates) do
- shas[#shas+1] = t.newsha
- end
- local ok, err = repo:update_modified_date(shas)
- if not ok then
- gitano.log.warn(err)
+function post_receive_run_supple(repo, updates)
+ if repo:uses_hook("post-receive") then
+ gitano.log.debug("Configuring for post-receive hook")
+ gitano.actions.set_supple_globals("post-receive")
+
+ local msg = gitano.i18n.expand("RUNNING_POST_RECEIVE_HOOK")
+ gitano.log.info(msg)
+ gitano.log.syslog.info(msg)
+
+ local info = {
+ username = username,
+ keytag = keytag,
+ source = source,
+ realname = (config.users[username] or {}).real_name or "",
+ email = (config.users[username] or {}).email_address or "",
+ }
+ local ok, msg = gitano.supple.run_hook("post-receive", repo, info, updates)
+ if not ok then
+ gitano.log.crit(msg or gitano.i18n.expand("ERROR_NO_ERROR_FOUND"))
+ end
+ gitano.log.info(gitano.i18n.expand("FINISHED"))
end
+ return "continue"
end
-if repo:uses_hook("post-receive") then
- gitano.log.debug("Configuring for post-receive hook")
- gitano.actions.set_supple_globals("post-receive")
+gitano.hooks.add(gitano.hooks.names.POST_RECEIVE, -1000,
+ post_receive_core_handler)
+gitano.hooks.add(gitano.hooks.names.POST_RECEIVE, 0, post_receive_run_supple)
- local msg = gitano.i18n.expand("RUNNING_POST_RECEIVE_HOOK")
- gitano.log.info(msg)
- gitano.log.syslog.info(msg)
-
- local info = {
- username = username,
- keytag = keytag,
- source = source,
- realname = (config.users[username] or {}).real_name or "",
- email = (config.users[username] or {}).email_address or "",
- }
- local ok, msg = gitano.supple.run_hook("post-receive", repo, info, updates)
- if not ok then
- gitano.log.crit(msg or gitano.i18n.expand("ERROR_NO_ERROR_FOUND"))
- end
- gitano.log.info(gitano.i18n.expand("FINISHED"))
-end
+gitano.hooks.run(gitano.hooks.names.POST_RECEIVE, repo, updates)
gitano.log.syslog.close()
diff --git a/lang/en.lua b/lang/en.lua
index ff69968..96af43b 100644
--- a/lang/en.lua
+++ b/lang/en.lua
@@ -162,7 +162,8 @@ example administration repository rules and an admin user and group.
BYPASS_USER_BANNER_HEADER = "**** ALERT **** ALERT **** PAY CAREFUL ATTENTION **** ALERT **** ALERT ****",
BYPASS_USER_ALERT_MESSAGE = "**** You are acting as the bypass user. Rules and hooks WILL NOT APPLY ****",
BYPASS_USER_BANNER_FOOTER = "**** ALERT **** ALERT **** DO NOT DO THIS NORMALLY **** ALERT **** ALERT ****",
-
+ PREAUTH_CMDLINE_HOOK_DECLINED = "Pre-authorization command line hook declined to permit action: ${reason}",
+ PREAUTH_CMDLINE_HOOK_ABORTED = "Pre-authorization command line hook aborted: ${reason}",
-- Messages from the config module
NO_SITE_CONF = "No site.conf",
NO_CORE_RULES = "No core rules file",
diff --git a/lib/gitano.lua b/lib/gitano.lua
index 38c22ef..5354a0f 100644
--- a/lib/gitano.lua
+++ b/lib/gitano.lua
@@ -41,9 +41,13 @@ local supple = require 'gitano.supple'
local auth = require 'gitano.auth'
local plugins = require 'gitano.plugins'
local i18n = require 'gitano.i18n'
+<<<<<<< HEAD
local patterns = require 'gitano.patterns'
+=======
+local hooks = require 'gitano.hooks'
+>>>>>>> dsilvers/hooks
-local _VERSION = {1, 0, 0}
+local _VERSION = {1, 1, 0}
_VERSION.major = _VERSION[1]
_VERSION.minor = _VERSION[2]
_VERSION.patch = _VERSION[3]
@@ -70,4 +74,5 @@ return {
plugins = plugins,
i18n = i18n,
patterns = patterns,
+ hooks = hooks,
}
diff --git a/lib/gitano/auth.lua b/lib/gitano/auth.lua
index c5a1095..bf3260f 100644
--- a/lib/gitano/auth.lua
+++ b/lib/gitano/auth.lua
@@ -37,6 +37,7 @@ local log = require 'gitano.log'
local repository = require 'gitano.repository'
local util = require 'gitano.util'
local i18n = require 'gitano.i18n'
+local hooks = require 'gitano.hooks'
local gall = require 'gall'
local luxio = require 'luxio'
@@ -121,6 +122,23 @@ local function is_authorized(user, source, cmdline, repo_root,
i18n.expand("CLIENT_CONNECTED",
{ ip=ip, user=user, key=keytag, cmdline=cmdline}))
+ local cancel
+ cancel, ip, user, keytag, parsed_cmdline =
+ (function(c,i,u,k,...)
+ return c, i, u, k, {...}
+ end)(hooks.run(hooks.names.PREAUTH_CMDLINE, false,
+ ip, user, keytag, unpack(parsed_cmdline)))
+
+ if cancel == nil then
+ log.syslog.err(i18n.expand("PREAUTH_CMDLINE_HOOK_ABORTED", {reason=ip}))
+ log.critical(i18n.expand("PREAUTH_CMDLINE_HOOK_DECLINED", {reason=ip}))
+ return nil
+ end
+ if cancel then
+ log.critical(i18n.expand("PREAUTH_CMDLINE_HOOK_DECLINED", {reason=ip}))
+ return nil
+ end
+
local cmd = command.get(parsed_cmdline[1])
if not cmd then
diff --git a/lib/gitano/hooks.lua b/lib/gitano/hooks.lua
new file mode 100644
index 0000000..0c3cba9
--- /dev/null
+++ b/lib/gitano/hooks.lua
@@ -0,0 +1,137 @@
+-- gitano.hooks
+--
+-- Hook management routines for Gitano
+--
+-- 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 hooks = {}
+
+-- In order to centralise and ensure hook names are good, these
+-- are the names of the hooks Gitano uses internally.
+-- Plugins are at liberty to add their own hooks if they want, but
+-- Gitano is unlikely to call them itself.
+
+local hook_names = {
+ -- Called by gitano.auth.is_authorized to allow manipulation of the
+ -- command line if wanted. Hook functions should take the form:
+ -- function preauth_cmdline_hook(cancel, config, ip, user, keytag, ...)
+ -- -- Decide what to do, if we're rejecting the command then set cancel
+ -- -- to something truthy such as:
+ -- return "stop", true, "Some reason"
+ -- -- otherwise try
+ -- return "continue"
+ -- -- or
+ -- return "update", cancel, config, ip, user, keytag, ...
+ -- end
+ PREAUTH_CMDLINE = "PREAUTH_CMDLINE",
+ -- Called by gitano-post-receive to allow processing to occur on the git
+ -- post-receive event if needed. The hook carries all the usual functions
+ -- as well as any registered by plugins.
+ -- Core admin stuff (running gitano-admin updates, update-server-info, etc)
+ -- runs at -1000. The supple hooks run at 0. Hook functions take the form:
+ -- function post_receive_hook(repo, updates)
+ -- -- Decide what to do. If we want to stop the hooks, return "stop"
+ -- -- but only do that if we MUST, since it will alter expected behaviour.
+ -- return "stop"
+ -- -- Otherwise, normally we'd just continue
+ -- return "continue"
+ -- -- Finally we can update if we want to alter the updates table
+ -- return "update", repo, different_updates
+ -- end
+ POST_RECEIVE = "POST_RECEIVE",
+}
+
+local function _get_hook(hookname)
+ local ret = hooks[hookname] or {}
+ hooks[hookname] = ret
+ return ret
+end
+
+local function _sort(hooktab)
+ table.sort(hooktab, function(a,b) return a[1] < b[1] end)
+end
+
+local function add_to_hook(hookname, priority, func)
+ assert(hookname, "Cannot add to a nil hook")
+ assert(type(priority) == "number", "Cannot use a non-numerical priority")
+ assert(type(func) == "function", "Cannot use a non-function hook func")
+ local h = _get_hook(hookname)
+ h[#h+1] = {priority, func}
+end
+
+local function _allbutone(_, ...)
+ return ...
+end
+
+local function run_hook(hookname, ...)
+ assert(hookname, "Cannot run a nil hook")
+ local h = _get_hook(hookname)
+ _sort(h)
+ local args = {...}
+ for _, entry in ipairs(h) do
+ local result = { entry[2](unpack(args)) }
+ if result[1] == nil then
+ return unpack(result)
+ elseif type(result[1]) ~= "string" then
+ return nil, "Bad results", unpack(result)
+ elseif result[1] == "stop" then
+ return _allbutone(unpack(result))
+ elseif result[1] == "update" then
+ args = {_allbutone(unpack(result))}
+ elseif result[1] == "continue" then
+ -- Nothing to do
+ else
+ return nil, "Bad results", unpack(result)
+ end
+ end
+ return unpack(args)
+end
+
+-- Hook functions take the form:
+-- action, ... = hookfunc(...)
+-- where the ... is chained through, and returned verbatim at
+-- the end.
+-- action can be nil (on error) or else one of:
+-- continue --> call the next hook function if there is one (not chaining ...)
+-- update --> as for 'continue' but chaining the ...
+-- stop --> stop now and return the rest of the results.
+
+-- Wherever Gitano registers a hook for something, the hook priority
+-- of zero will be Gitano's action. So if you want to alter what is
+-- passed to Gitano's default behaviour, register with a negative value
+-- and if you want to just do more afterwards, register with a positive
+-- value
+
+return {
+ add = add_to_hook,
+ run = run_hook,
+ names = hook_names,
+}
diff --git a/plugins/testing-hooks.lua b/plugins/testing-hooks.lua
new file mode 100644
index 0000000..f428001
--- /dev/null
+++ b/plugins/testing-hooks.lua
@@ -0,0 +1,76 @@
+-- Plugin for testing hooks
+--
+-- This is a testing plugin which will not be installed as part of
+-- Gitano. Its purpose is to allow the test suite to verify various parts
+-- of the hooking system in Gitano.
+--
+-- Copyright 2017 Daniel Silverstone <daniel.silverstone@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 function _check(what, hookname)
+ local aborts = "," .. (os.getenv("HOOK_"..what) or "") .. ","
+ return (aborts:find("," .. hookname .. ","))
+end
+
+local function _abort(...) return _check("ABORT", ...) end
+local function _decline(...) return _check("DECLINE", ...) end
+
+-- This function allows us to verify behaviour of the preauth_cmdline hook
+local function preauth_cmdline_hookfunc(cancel, ip, user, keytag, cmd, ...)
+ if _abort("PREAUTH_CMDLINE") then
+ return nil, "Aborted on request"
+ end
+ if _decline("PREAUTH_CMDLINE") then
+ return "stop", true, "Declined on request"
+ end
+ if os.getenv("PREAUTH_CMDLINE_REMOVEME") and cmd == "removeme" then
+ return "update", cancel, ip, user, keytag, ...
+ end
+ return "continue"
+end
+
+gitano.hooks.add(gitano.hooks.names.PREAUTH_CMDLINE,
+ 10, preauth_cmdline_hookfunc)
+
+-- This function is meant to allow us to check that the POST_RECEIVE hook works
+local function post_receive_hookfunc(repo, updates)
+ if _abort("POST_RECEIVE") then
+ return nil, "Aborted on request"
+ end
+ if _decline("POST_RECEIVE") then
+ io.stdout:write("HOOKFUNC_STOPPED")
+ io.stdout:flush()
+ return "stop"
+ end
+ return "continue"
+end
+
+-- We deliberately install the hook between core behaviour and supple
+gitano.hooks.add(gitano.hooks.names.POST_RECEIVE,
+ -100, post_receive_hookfunc)
diff --git a/testing/01-hooks.yarn b/testing/01-hooks.yarn
new file mode 100644
index 0000000..b3e4413
--- /dev/null
+++ b/testing/01-hooks.yarn
@@ -0,0 +1,73 @@
+<!-- -*- markdown -*- -->
+Basic hook support tests
+========================
+
+In these tests we verify the various hooks function at some basic level.
+
+For example, we check that we can abort some of the hooks and that we can
+alter behaviour or add behaviour to certain hooks which might commonly be
+used for the sorts of things plugins want.
+
+Preauthorization commandline hook
+---------------------------------
+
+The preauth_cmdline hook is used to allow plugins to adjust (or reject) the
+parsed command line before Gitano even looks up what command it might be for.
+This could be used to add aliases for certain commands, or just stop things
+from happening...
+
+ SCENARIO preauth_cmdline can be manipulated
+ GIVEN a standard instance
+
+ WHEN testinstance adminkey runs ls
+ THEN stdout contains gitano-admin
+
+ GIVEN HOOK_ABORT is in the environment set to PREAUTH_CMDLINE
+
+ WHEN testinstance adminkey, expecting failure, runs ls
+ THEN stderr contains Aborted on request
+
+ GIVEN HOOK_ABORT is not in the environment
+ AND HOOK_DECLINE is in the environment set to PREAUTH_CMDLINE
+
+ WHEN testinstance adminkey, expecting failure, runs ls
+ THEN stderr contains Declined on request
+
+ GIVEN HOOK_DECLINE is not in the environment
+ AND PREAUTH_CMDLINE_REMOVEME is in the environment set to 1
+
+ WHEN testinstance adminkey runs removeme ls
+ THEN stdout contains gitano-admin
+
+ GIVEN PREAUTH_CMDLINE_REMOVEME is not in the environment
+
+ WHEN testinstance adminkey, expecting failure, runs removeme ls
+ THEN stderr contains removeme
+
+ FINALLY the instance is torn down
+
+Post Receieve hook
+------------------
+
+The `POST_RECEIVE` hook allows plugins to perform actions during post-receive.
+This is after the commits have made it into the repository, and after the refs
+have been updated. The `POST_RECEIVE` hook gets given the set of updates which
+were applied to the repository and it gets to take action. Generally we don't
+recommend that hooks _stop_ the chain, but they can, which lets us do things
+like preventing Supple running.
+
+ SCENARIO supple isn't even considered when post_receive hooks "stop"
+ ASSUMING gitano is being accessed over ssh
+
+ GIVEN a standard instance
+ AND testinstance using adminkey has patched gitano-admin with post-receive-alert.patch
+ AND HOOK_DECLINE is in the environment set to POST_RECEIVE
+ WHEN testinstance using adminkey clones gitano-admin.git as gitano-admin
+ AND testinstance using adminkey pushes an empty commit in gitano-admin
+ WHEN testinstance using bypasskey pushes an empty commit in gitano-admin
+ THEN the output does not contain PERIL
+ AND the output does not contain CRITICAL FAILURE
+ AND the output does not contain XYZZY
+ AND the output contains HOOKFUNC_STOPPED
+
+ FINALLY the instance is torn down
diff --git a/testing/gitano-test-tool.in b/testing/gitano-test-tool.in
index f467918..1fd39b0 100644
--- a/testing/gitano-test-tool.in
+++ b/testing/gitano-test-tool.in
@@ -70,10 +70,28 @@ local function unix_assert(ret, errno)
end
end
+local function load_env(into)
+ local f, msg = loadfile(basedir .. ".gtt-env")
+ if f then
+ setfenv(f, into)
+ f()
+ end
+end
+
+local function save_env(env)
+ local f = io.open(basedir .. ".gtt-env", "w")
+ for k, v in pairs(env) do
+ f:write(("%s = %q\n"):format(k, v))
+ end
+ f:close()
+end
+
local function run_program(t)
- if t.env and os.getenv("GITANO_DUMP_VARIABLE_FILE") then
+ t.env = (t.env or {})
+ if os.getenv("GITANO_DUMP_VARIABLE_FILE") then
t.env["GITANO_DUMP_VARIABLE_FILE"] = os.getenv("GITANO_DUMP_VARIABLE_FILE")
end
+ load_env(t.env)
local f = io.open(basedir .. "last-program", "w")
local function print (...)
f:write(...)
@@ -180,6 +198,20 @@ function cmd_setgitconfig(username, key, value)
}
end
+function cmd_setenv(key, value)
+ local t = {}
+ load_env(t)
+ t[key] = value
+ save_env(t)
+end
+
+function cmd_unsetenv(key)
+ local t = {}
+ load_env(t)
+ t[key] = nil
+ save_env(t)
+end
+
function cmd_createunixuser(username)
assert(sio.mkdir(user_home(username), "0755"))
assert(sio.mkdir(ssh_base(username), "0755"))
diff --git a/testing/library.yarn b/testing/library.yarn
index 6facf2a..a370bfe 100644
--- a/testing/library.yarn
+++ b/testing/library.yarn
@@ -283,6 +283,12 @@ Generic utility methods
IMPLEMENTS ASSUMING gitano is being accessed over ([^ ]+)
test "$GTT_PROTO" = "$MATCH_1"
+ IMPLEMENTS GIVEN ([^ ]+) is in the environment set to (.+)
+ $GTT setenv "$MATCH_1" "$MATCH_2"
+
+ IMPLEMENTS GIVEN ([^ ]+) is not in the environment
+ $GTT unsetenv "$MATCH_1"
+
GPG Keyring related helpers
---------------------------