2016-06-10 21:59:52 +02:00
|
|
|
from pathlib import Path
|
|
|
|
|
from collections import namedtuple
|
|
|
|
|
from git import Repo, InvalidGitRepositoryError, NoSuchPathError
|
|
|
|
|
from subprocess import Popen
|
|
|
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
|
|
|
from threading import RLock
|
|
|
|
|
import sys
|
|
|
|
|
import logging
|
|
|
|
|
import shlex
|
|
|
|
|
import os
|
2016-06-11 01:07:18 +02:00
|
|
|
import atexit
|
2016-06-10 21:59:52 +02:00
|
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
2016-06-11 13:19:54 +02:00
|
|
|
BUILD_REPO_DIR = "{name}_build_repo"
|
|
|
|
|
OUTPUT_DIR = "{name}_output"
|
2016-06-10 21:59:52 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class DeploymentRunner:
|
2016-06-10 23:34:06 +02:00
|
|
|
|
2016-06-10 21:59:52 +02:00
|
|
|
def __init__(self, name, runner_config):
|
|
|
|
|
self.name = name
|
|
|
|
|
self.working_directory = Path(runner_config["working_directory"])
|
|
|
|
|
if not self.working_directory.exists():
|
|
|
|
|
log.info("creating working directory for %s: %s", name,
|
|
|
|
|
self.working_directory)
|
|
|
|
|
self.working_directory.mkdir(parents=True)
|
|
|
|
|
self.working_directory = self.working_directory.resolve()
|
|
|
|
|
|
|
|
|
|
self.clone_url = runner_config["clone_url"]
|
|
|
|
|
self.git_branch = runner_config["git_branch"]
|
|
|
|
|
self.target_directory = runner_config["target_directory"]
|
2016-06-11 13:19:54 +02:00
|
|
|
self.build_repo_path = self.working_directory / BUILD_REPO_DIR.format(
|
|
|
|
|
name=name)
|
2016-06-11 22:04:27 +02:00
|
|
|
self.build_command = runner_config["build_command"].format(
|
2016-06-11 13:19:54 +02:00
|
|
|
output=OUTPUT_DIR.format(name=name))
|
2016-06-11 15:38:59 +02:00
|
|
|
self._build_proc_env = dict(os.environ,
|
2016-06-11 22:04:27 +02:00
|
|
|
**runner_config.get("build_env", {}))
|
2016-06-10 21:59:52 +02:00
|
|
|
|
2016-06-11 15:38:59 +02:00
|
|
|
self._executor = ThreadPoolExecutor(max_workers=1)
|
|
|
|
|
self._futures = set()
|
|
|
|
|
self._build_proc = None
|
|
|
|
|
self._abort = False
|
2016-06-10 21:59:52 +02:00
|
|
|
self._build_lock = RLock()
|
|
|
|
|
|
|
|
|
|
def update_build_repository(self):
|
|
|
|
|
try:
|
|
|
|
|
build_repo = Repo(str(self.build_repo_path))
|
|
|
|
|
|
|
|
|
|
except (InvalidGitRepositoryError, NoSuchPathError) as e:
|
2016-06-10 23:34:06 +02:00
|
|
|
if self.build_repo_path.is_dir() and \
|
2016-06-10 21:59:52 +02:00
|
|
|
next(self.build_repo_path.iterdir(), None) is not None:
|
2016-06-11 13:19:54 +02:00
|
|
|
log.error(
|
|
|
|
|
"non-empty %s exists but not a valid git repository!",
|
|
|
|
|
self.build_repo_path)
|
2016-06-10 21:59:52 +02:00
|
|
|
raise
|
|
|
|
|
else:
|
|
|
|
|
log.info("Build repository %s not there, cloneing", e)
|
|
|
|
|
build_repo = Repo.clone_from(self.clone_url,
|
|
|
|
|
str(self.build_repo_path),
|
|
|
|
|
branch=self.git_branch)
|
|
|
|
|
|
2016-06-10 23:34:06 +02:00
|
|
|
if build_repo.remotes.origin.url != self.clone_url:
|
|
|
|
|
cw = build_repo.remotes.origin.config_writer
|
|
|
|
|
cw.set("url", self.clone_url)
|
|
|
|
|
cw.release()
|
|
|
|
|
|
2016-06-10 21:59:52 +02:00
|
|
|
build_repo.head.reference = build_repo.create_head(self.git_branch)
|
|
|
|
|
assert not build_repo.head.is_detached
|
|
|
|
|
|
2016-06-10 23:34:06 +02:00
|
|
|
# deinit submodules to avoid removed ones dangling around later
|
|
|
|
|
# they should stay around in .git, so reinit should be fast
|
|
|
|
|
build_repo.git.submodule("deinit", ".")
|
|
|
|
|
|
2016-06-10 21:59:52 +02:00
|
|
|
build_repo.remotes.origin.pull(
|
|
|
|
|
force=True,
|
|
|
|
|
no_edit=True,
|
2016-06-10 23:34:06 +02:00
|
|
|
refspec="+{b}:{b}".format(b=self.git_branch),
|
|
|
|
|
recurse_submodules="yes")
|
2016-06-10 21:59:52 +02:00
|
|
|
|
|
|
|
|
# forcefully reset the working tree
|
|
|
|
|
build_repo.head.reset(index=True, working_tree=True)
|
|
|
|
|
try:
|
2016-06-11 21:20:45 +02:00
|
|
|
build_repo.git.clean(force=True, d=True, x=True)
|
2016-06-10 21:59:52 +02:00
|
|
|
except:
|
|
|
|
|
log.warning("git clean failed!", exc_info=True)
|
|
|
|
|
|
2016-06-10 23:34:06 +02:00
|
|
|
# update the submodules
|
|
|
|
|
build_repo.git.submodule("update", "--init", "--force", "--recursive")
|
|
|
|
|
|
2016-06-10 21:59:52 +02:00
|
|
|
def build(self, abort_running=False):
|
|
|
|
|
with self._build_lock:
|
|
|
|
|
if abort_running:
|
|
|
|
|
self.try_abort_build()
|
|
|
|
|
|
|
|
|
|
# cancel everything, so we are next
|
2016-06-11 15:38:59 +02:00
|
|
|
for fut in self._futures.copy():
|
2016-06-10 21:59:52 +02:00
|
|
|
fut.cancel()
|
|
|
|
|
if fut.done():
|
2016-06-11 15:38:59 +02:00
|
|
|
self._futures.remove(fut)
|
2016-06-10 21:59:52 +02:00
|
|
|
|
2016-06-11 15:38:59 +02:00
|
|
|
self._futures.add(self._executor.submit(self.build_blocking))
|
2016-06-10 21:59:52 +02:00
|
|
|
|
|
|
|
|
def try_abort_build(self):
|
2016-06-11 15:38:59 +02:00
|
|
|
proc = self._build_proc
|
|
|
|
|
self._abort = True
|
2016-06-10 21:59:52 +02:00
|
|
|
if proc:
|
|
|
|
|
proc.kill()
|
|
|
|
|
|
|
|
|
|
def build_blocking(self):
|
2016-06-11 15:38:59 +02:00
|
|
|
self._abort = False
|
2016-06-10 21:59:52 +02:00
|
|
|
|
|
|
|
|
# preparing build environment
|
|
|
|
|
self.update_build_repository()
|
|
|
|
|
# TODO: prepare_output()
|
|
|
|
|
|
|
|
|
|
# start the build if we should not abort
|
2016-06-11 15:38:59 +02:00
|
|
|
if not self._abort:
|
2016-06-11 22:04:27 +02:00
|
|
|
args = shlex.split(self.build_command)
|
2016-06-11 15:38:59 +02:00
|
|
|
self._build_proc = Popen(args,
|
|
|
|
|
cwd=str(self.build_repo_path),
|
|
|
|
|
env=self._build_proc_env)
|
|
|
|
|
atexit.register(self._build_proc.kill)
|
|
|
|
|
status = self._build_proc.wait()
|
|
|
|
|
atexit.unregister(self._build_proc.kill)
|
2016-06-11 01:07:18 +02:00
|
|
|
|
2016-06-10 21:59:52 +02:00
|
|
|
if status == 0:
|
|
|
|
|
# TODO: postproc...
|
|
|
|
|
pass
|