scm-wrapper: Allow running command after git clone
When a file should be obtained from a git repository, allow running an arbitrary command (like `make`) after clone but before copying the files out. This only works for the Git backend. The downside is that a clone is needed and we can no longer use `git archive` to speed things up. Fixes: https://pagure.io/pungi/issue/5 Signed-off-by: Lubomír Sedlář <lsedlar@redhat.com>
This commit is contained in:
parent
f9640ae0b4
commit
cae202c17b
@ -422,6 +422,7 @@ def _make_schema():
|
|||||||
"branch": {"$ref": "#/definitions/optional_string"},
|
"branch": {"$ref": "#/definitions/optional_string"},
|
||||||
"file": {"type": "string"},
|
"file": {"type": "string"},
|
||||||
"dir": {"type": "string"},
|
"dir": {"type": "string"},
|
||||||
|
"command": {"type": "string"},
|
||||||
},
|
},
|
||||||
"additionalProperties": False,
|
"additionalProperties": False,
|
||||||
},
|
},
|
||||||
|
@ -27,8 +27,9 @@ from pungi.util import (explode_rpm_package, makedirs, copy_all, temp_dir,
|
|||||||
|
|
||||||
|
|
||||||
class ScmBase(kobo.log.LoggingBase):
|
class ScmBase(kobo.log.LoggingBase):
|
||||||
def __init__(self, logger=None):
|
def __init__(self, logger=None, command=None):
|
||||||
kobo.log.LoggingBase.__init__(self, logger=logger)
|
kobo.log.LoggingBase.__init__(self, logger=logger)
|
||||||
|
self.command = command
|
||||||
|
|
||||||
@retry(interval=60, timeout=300, wait_on=RuntimeError)
|
@retry(interval=60, timeout=300, wait_on=RuntimeError)
|
||||||
def retry_run(self, cmd, **kwargs):
|
def retry_run(self, cmd, **kwargs):
|
||||||
@ -39,6 +40,15 @@ class ScmBase(kobo.log.LoggingBase):
|
|||||||
|
|
||||||
return run(cmd, **kwargs)
|
return run(cmd, **kwargs)
|
||||||
|
|
||||||
|
def run_process_command(self, cwd):
|
||||||
|
if self.command:
|
||||||
|
self.log_debug('Running "%s"' % self.command)
|
||||||
|
retcode, output = run(self.command, workdir=cwd, can_fail=True)
|
||||||
|
if retcode != 0:
|
||||||
|
self.log_error('Output was: "%s"' % output)
|
||||||
|
raise RuntimeError('%r failed with exit code %s'
|
||||||
|
% (self.command, retcode))
|
||||||
|
|
||||||
|
|
||||||
class FileWrapper(ScmBase):
|
class FileWrapper(ScmBase):
|
||||||
def export_dir(self, scm_root, scm_dir, target_dir, scm_branch=None):
|
def export_dir(self, scm_root, scm_dir, target_dir, scm_branch=None):
|
||||||
@ -104,10 +114,11 @@ class GitWrapper(ScmBase):
|
|||||||
% (pipes.quote(scm_root), pipes.quote(scm_branch), pipes.quote(scm_dir)))
|
% (pipes.quote(scm_root), pipes.quote(scm_branch), pipes.quote(scm_dir)))
|
||||||
# git archive is not supported by http/https
|
# git archive is not supported by http/https
|
||||||
# or by smart http https://git-scm.com/book/en/v2/Git-on-the-Server-Smart-HTTP
|
# or by smart http https://git-scm.com/book/en/v2/Git-on-the-Server-Smart-HTTP
|
||||||
if scm_root.startswith("http"):
|
if scm_root.startswith("http") or self.command:
|
||||||
cmd = ("/usr/bin/git clone --depth 1 --branch=%s %s %s"
|
cmd = ("/usr/bin/git clone --depth 1 --branch=%s %s %s"
|
||||||
% (pipes.quote(scm_branch), pipes.quote(scm_root), pipes.quote(tmp_dir)))
|
% (pipes.quote(scm_branch), pipes.quote(scm_root), pipes.quote(tmp_dir)))
|
||||||
self.retry_run(cmd, workdir=tmp_dir, show_cmd=True)
|
self.retry_run(cmd, workdir=tmp_dir, show_cmd=True)
|
||||||
|
self.run_process_command(tmp_dir)
|
||||||
|
|
||||||
copy_all(os.path.join(tmp_dir, scm_dir), target_dir)
|
copy_all(os.path.join(tmp_dir, scm_dir), target_dir)
|
||||||
|
|
||||||
@ -127,10 +138,11 @@ class GitWrapper(ScmBase):
|
|||||||
% (pipes.quote(scm_root), pipes.quote(scm_branch), pipes.quote(scm_file)))
|
% (pipes.quote(scm_root), pipes.quote(scm_branch), pipes.quote(scm_file)))
|
||||||
# git archive is not supported by http/https
|
# git archive is not supported by http/https
|
||||||
# or by smart http https://git-scm.com/book/en/v2/Git-on-the-Server-Smart-HTTP
|
# or by smart http https://git-scm.com/book/en/v2/Git-on-the-Server-Smart-HTTP
|
||||||
if scm_root.startswith("http"):
|
if scm_root.startswith("http") or self.command:
|
||||||
cmd = ("/usr/bin/git clone --depth 1 --branch=%s %s %s"
|
cmd = ("/usr/bin/git clone --depth 1 --branch=%s %s %s"
|
||||||
% (pipes.quote(scm_branch), pipes.quote(scm_root), pipes.quote(tmp_dir)))
|
% (pipes.quote(scm_branch), pipes.quote(scm_root), pipes.quote(tmp_dir)))
|
||||||
self.retry_run(cmd, workdir=tmp_dir, show_cmd=True)
|
self.retry_run(cmd, workdir=tmp_dir, show_cmd=True)
|
||||||
|
self.run_process_command(tmp_dir)
|
||||||
|
|
||||||
makedirs(target_dir)
|
makedirs(target_dir)
|
||||||
shutil.copy2(os.path.join(tmp_dir, scm_file), target_path)
|
shutil.copy2(os.path.join(tmp_dir, scm_file), target_path)
|
||||||
@ -219,13 +231,15 @@ def get_file_from_scm(scm_dict, target_path, logger=None):
|
|||||||
scm_repo = None
|
scm_repo = None
|
||||||
scm_file = os.path.abspath(scm_dict)
|
scm_file = os.path.abspath(scm_dict)
|
||||||
scm_branch = None
|
scm_branch = None
|
||||||
|
command = None
|
||||||
else:
|
else:
|
||||||
scm_type = scm_dict["scm"]
|
scm_type = scm_dict["scm"]
|
||||||
scm_repo = scm_dict["repo"]
|
scm_repo = scm_dict["repo"]
|
||||||
scm_file = scm_dict["file"]
|
scm_file = scm_dict["file"]
|
||||||
scm_branch = scm_dict.get("branch", None)
|
scm_branch = scm_dict.get("branch", None)
|
||||||
|
command = scm_dict.get('command')
|
||||||
|
|
||||||
scm = _get_wrapper(scm_type, logger=logger)
|
scm = _get_wrapper(scm_type, logger=logger, command=command)
|
||||||
|
|
||||||
files_copied = []
|
files_copied = []
|
||||||
for i in force_list(scm_file):
|
for i in force_list(scm_file):
|
||||||
@ -270,13 +284,15 @@ def get_dir_from_scm(scm_dict, target_path, logger=None):
|
|||||||
scm_repo = None
|
scm_repo = None
|
||||||
scm_dir = os.path.abspath(scm_dict)
|
scm_dir = os.path.abspath(scm_dict)
|
||||||
scm_branch = None
|
scm_branch = None
|
||||||
|
command = None
|
||||||
else:
|
else:
|
||||||
scm_type = scm_dict["scm"]
|
scm_type = scm_dict["scm"]
|
||||||
scm_repo = scm_dict.get("repo", None)
|
scm_repo = scm_dict.get("repo", None)
|
||||||
scm_dir = scm_dict["dir"]
|
scm_dir = scm_dict["dir"]
|
||||||
scm_branch = scm_dict.get("branch", None)
|
scm_branch = scm_dict.get("branch", None)
|
||||||
|
command = scm_dict.get("command")
|
||||||
|
|
||||||
scm = _get_wrapper(scm_type, logger=logger)
|
scm = _get_wrapper(scm_type, logger=logger, command=command)
|
||||||
|
|
||||||
with temp_dir(prefix="scm_checkout_") as tmp_dir:
|
with temp_dir(prefix="scm_checkout_") as tmp_dir:
|
||||||
scm.export_dir(scm_repo, scm_dir, scm_branch=scm_branch, target_dir=tmp_dir)
|
scm.export_dir(scm_repo, scm_dir, scm_branch=scm_branch, target_dir=tmp_dir)
|
||||||
|
@ -109,6 +109,7 @@ class GitSCMTestCase(SCMBaseTest):
|
|||||||
commands = []
|
commands = []
|
||||||
|
|
||||||
def process(cmd, workdir=None, **kwargs):
|
def process(cmd, workdir=None, **kwargs):
|
||||||
|
if cmd.startswith('/usr/bin/git'):
|
||||||
fname = cmd.split('|')[0].strip().split(' ')[-1]
|
fname = cmd.split('|')[0].strip().split(' ')[-1]
|
||||||
touch(os.path.join(workdir, fname))
|
touch(os.path.join(workdir, fname))
|
||||||
commands.append(cmd)
|
commands.append(cmd)
|
||||||
@ -124,6 +125,76 @@ class GitSCMTestCase(SCMBaseTest):
|
|||||||
commands,
|
commands,
|
||||||
['/usr/bin/git archive --remote=git://example.com/git/repo.git master some_file.txt | tar xf -'])
|
['/usr/bin/git archive --remote=git://example.com/git/repo.git master some_file.txt | tar xf -'])
|
||||||
|
|
||||||
|
@mock.patch('pungi.wrappers.scm.run')
|
||||||
|
def test_get_file_generated_by_command_via_git(self, run):
|
||||||
|
commands = []
|
||||||
|
|
||||||
|
def process(cmd, workdir=None, **kwargs):
|
||||||
|
if cmd.startswith('/usr/bin/git'):
|
||||||
|
checkout = cmd.split(' ')[-1]
|
||||||
|
touch(os.path.join(checkout, 'some_file.txt'))
|
||||||
|
commands.append(cmd)
|
||||||
|
return 0, ''
|
||||||
|
|
||||||
|
run.side_effect = process
|
||||||
|
|
||||||
|
retval = scm.get_file_from_scm({'scm': 'git',
|
||||||
|
'repo': 'git://example.com/git/repo.git',
|
||||||
|
'file': 'some_file.txt',
|
||||||
|
'command': 'make'},
|
||||||
|
self.destdir)
|
||||||
|
self.assertStructure(retval, ['some_file.txt'])
|
||||||
|
self.assertRegexpMatches(
|
||||||
|
commands[0],
|
||||||
|
r'/usr/bin/git clone --depth 1 --branch=master git://example.com/git/repo.git /tmp/.+')
|
||||||
|
self.assertEqual(commands[1:], ['make'])
|
||||||
|
|
||||||
|
@mock.patch('pungi.wrappers.scm.run')
|
||||||
|
def test_get_file_and_fail_to_generate(self, run):
|
||||||
|
commands = []
|
||||||
|
|
||||||
|
def process(cmd, workdir=None, **kwargs):
|
||||||
|
if cmd.startswith('/usr/bin/git'):
|
||||||
|
checkout = cmd.split(' ')[-1]
|
||||||
|
touch(os.path.join(checkout, 'some_file.txt'))
|
||||||
|
commands.append(cmd)
|
||||||
|
return len(commands), 'output'
|
||||||
|
|
||||||
|
run.side_effect = process
|
||||||
|
|
||||||
|
with self.assertRaises(RuntimeError) as ctx:
|
||||||
|
scm.get_file_from_scm({'scm': 'git',
|
||||||
|
'repo': 'git://example.com/git/repo.git',
|
||||||
|
'file': 'some_file.txt',
|
||||||
|
'command': 'make'},
|
||||||
|
self.destdir)
|
||||||
|
|
||||||
|
self.assertEqual(str(ctx.exception), "'make' failed with exit code 2")
|
||||||
|
|
||||||
|
@mock.patch('pungi.wrappers.scm.run')
|
||||||
|
def test_get_file_generated_by_command_via_https(self, run):
|
||||||
|
commands = []
|
||||||
|
|
||||||
|
def process(cmd, workdir=None, **kwargs):
|
||||||
|
if cmd.startswith('/usr/bin/git'):
|
||||||
|
checkout = cmd.split(' ')[-1]
|
||||||
|
touch(os.path.join(checkout, 'some_file.txt'))
|
||||||
|
commands.append(cmd)
|
||||||
|
return 0, ''
|
||||||
|
|
||||||
|
run.side_effect = process
|
||||||
|
|
||||||
|
retval = scm.get_file_from_scm({'scm': 'git',
|
||||||
|
'repo': 'https://example.com/git/repo.git',
|
||||||
|
'file': 'some_file.txt',
|
||||||
|
'command': 'make'},
|
||||||
|
self.destdir)
|
||||||
|
self.assertStructure(retval, ['some_file.txt'])
|
||||||
|
self.assertRegexpMatches(
|
||||||
|
commands[0],
|
||||||
|
r'/usr/bin/git clone --depth 1 --branch=master https://example.com/git/repo.git /tmp/.+')
|
||||||
|
self.assertEqual(commands[1:], ['make'])
|
||||||
|
|
||||||
@mock.patch('pungi.wrappers.scm.run')
|
@mock.patch('pungi.wrappers.scm.run')
|
||||||
def test_get_file_via_https(self, run):
|
def test_get_file_via_https(self, run):
|
||||||
commands = []
|
commands = []
|
||||||
@ -168,6 +239,32 @@ class GitSCMTestCase(SCMBaseTest):
|
|||||||
commands,
|
commands,
|
||||||
['/usr/bin/git archive --remote=git://example.com/git/repo.git master subdir | tar xf -'])
|
['/usr/bin/git archive --remote=git://example.com/git/repo.git master subdir | tar xf -'])
|
||||||
|
|
||||||
|
@mock.patch('pungi.wrappers.scm.run')
|
||||||
|
def test_get_dir_via_git_and_generate(self, run):
|
||||||
|
commands = []
|
||||||
|
|
||||||
|
def process(cmd, workdir=None, **kwargs):
|
||||||
|
if cmd.startswith('/usr/bin/git'):
|
||||||
|
checkout = cmd.split(' ')[-1]
|
||||||
|
touch(os.path.join(checkout, 'subdir', 'first'))
|
||||||
|
touch(os.path.join(checkout, 'subdir', 'second'))
|
||||||
|
commands.append(cmd)
|
||||||
|
return 0, ''
|
||||||
|
|
||||||
|
run.side_effect = process
|
||||||
|
|
||||||
|
retval = scm.get_dir_from_scm({'scm': 'git',
|
||||||
|
'repo': 'git://example.com/git/repo.git',
|
||||||
|
'dir': 'subdir',
|
||||||
|
'command': 'make'},
|
||||||
|
self.destdir)
|
||||||
|
self.assertStructure(retval, ['first', 'second'])
|
||||||
|
|
||||||
|
self.assertRegexpMatches(
|
||||||
|
commands[0],
|
||||||
|
r'/usr/bin/git clone --depth 1 --branch=master git://example.com/git/repo.git /tmp/.+')
|
||||||
|
self.assertEqual(commands[1:], ['make'])
|
||||||
|
|
||||||
@mock.patch('pungi.wrappers.scm.run')
|
@mock.patch('pungi.wrappers.scm.run')
|
||||||
def test_get_dir_via_https(self, run):
|
def test_get_dir_via_https(self, run):
|
||||||
commands = []
|
commands = []
|
||||||
@ -190,6 +287,32 @@ class GitSCMTestCase(SCMBaseTest):
|
|||||||
commands[0],
|
commands[0],
|
||||||
r'/usr/bin/git clone --depth 1 --branch=master https://example.com/git/repo.git /tmp/.+')
|
r'/usr/bin/git clone --depth 1 --branch=master https://example.com/git/repo.git /tmp/.+')
|
||||||
|
|
||||||
|
@mock.patch('pungi.wrappers.scm.run')
|
||||||
|
def test_get_dir_via_https_and_generate(self, run):
|
||||||
|
commands = []
|
||||||
|
|
||||||
|
def process(cmd, workdir=None, **kwargs):
|
||||||
|
if cmd.startswith('/usr/bin/git'):
|
||||||
|
checkout = cmd.split(' ')[-1]
|
||||||
|
touch(os.path.join(checkout, 'subdir', 'first'))
|
||||||
|
touch(os.path.join(checkout, 'subdir', 'second'))
|
||||||
|
commands.append(cmd)
|
||||||
|
return 0, ''
|
||||||
|
|
||||||
|
run.side_effect = process
|
||||||
|
|
||||||
|
retval = scm.get_dir_from_scm({'scm': 'git',
|
||||||
|
'repo': 'https://example.com/git/repo.git',
|
||||||
|
'dir': 'subdir',
|
||||||
|
'command': 'make'},
|
||||||
|
self.destdir)
|
||||||
|
self.assertStructure(retval, ['first', 'second'])
|
||||||
|
|
||||||
|
self.assertRegexpMatches(
|
||||||
|
commands[0],
|
||||||
|
r'/usr/bin/git clone --depth 1 --branch=master https://example.com/git/repo.git /tmp/.+')
|
||||||
|
self.assertEqual(commands[1:], ['make'])
|
||||||
|
|
||||||
|
|
||||||
class RpmSCMTestCase(SCMBaseTest):
|
class RpmSCMTestCase(SCMBaseTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user