def setup_pubpublica_access(c, ctx): user = ctx.get("USER") group = ctx.get("GROUP") with Guard("· creating user and group..."): if user: if not system.create_user(c, user, sudo=True): raise Exception(f"failed to create user '{user}'") if group: if not system.create_group(c, group, sudo=True): raise Exception(f"failed to create group '{group}") if not system.group_add_user(c, group, user, sudo=True): raise Exception( f"failed to add user '{user} to group '{group}") with Guard("· changing permissions..."): deploy_path = ctx.get("DEPLOY_PATH") if user: if not access.change_owner( c, deploy_path, user, recursive=True, sudo=True): raise Exception(f"failed to change owner of deployment") if group: if not access.change_group( c, deploy_path, group, recursive=True, sudo=True): raise Exception(f"failed to change group of deployment")
def check_local_git_repo(c, ctx): with Guard("· checking local git repo..."): root = ctx.get("LOCAL_APP_PATH") dirty = git.is_dirty(c, root) if dirty is None: raise GuardWarning(f"{root} is not a git repository") if dirty: raise GuardWarning("local git repository is dirty")
def setup_pubpublica_virtualenv(c, ctx): deploy_path = ctx.get("DEPLOY_PATH") venv_dir = os.path.join(deploy_path, "venv") with Guard("· creating virtual environment..."): create_venv = f"python3.8 -m venv {venv_dir}" ret = c.sudo(create_venv, hide=True, warn=True) if not ret.ok: raise Exception(f"failed creating virtual environment: {ret}") with Guard("· updating virtual environment..."): pip_file = os.path.join(venv_dir, "bin", "pip3.8") requirements_file = os.path.join(deploy_path, "requirements.txt") pip_install = f"{pip_file} install -r {requirements_file}" ret = c.sudo(pip_install, hide=True, warn=True) if not ret.ok: raise Exception(f"failed to update the virtual environment: {ret}")
def setup_nginx(c, ctx): # TODO: copy over nginx settings print("setting up nginx") with Guard("· building config files..."): if not (cfg := ctx.get("NGINX") or {}): pass config_path = ctx.get("LOCAL_CONFIG_PATH") nginx_template = os.path.join(config_path, ".nginx")
def check_deployment(c, ctx): with Guard("· checking deployment..."): app_path = ctx.get("APP_PATH") id_file = ctx.get("DEPLOYED_ID_FILE") deployment_file = os.path.join(app_path, id_file) id = fs.read_file(c, deployment_file) if not id: raise GuardWarning("unable to find deployed id") ctx.update({"DEPLOYED_ARTIFACT_ID": id})
def check_dependencies(c, ctx): with Guard("· checking dependencies..."): missing = [] deps = ctx.get("DEPENDENCIES") or [] for dep in deps: if not apt.is_installed(c, dep): missing.append(dep) if missing: raise Exception( f"the following dependencies are not installed: {missing}")
def unpack_project(c, ctx): with Guard("· unpacking..."): deploy_path = ctx.get("DEPLOY_PATH") artifact = ctx.get("ARTIFACT_FILE") artifact_path = os.path.join(deploy_path, artifact) cmd = f"tar -C {deploy_path} -xzf {artifact_path}" unpack = c.sudo(cmd, hide=True, warn=True) if not unpack.ok: raise Exception(f"failed to unpack project: {unpack.stderr}") if not fs.remove(c, artifact_path, sudo=True): raise GuardWarning("failed to remove artifact after unpacking")
def check_versions(c, ctx): with Guard("· checking versions..."): production_path = ctx.get("PRODUCTION_PATH") remote_ver_file = os.path.join(production_path, "__version__.py") v_remote = fs.read_file(c, remote_ver_file) if not v_remote: raise GuardWarning("unable to retrieve deployed version") ctx.update({"REMOTE_VERSION": v_remote}) v_local = ctx.get("LOCAL_VERSION") if not util.version_newer(v_local, v_remote): raise GuardWarning( f"{v_local} is older or equal to deployed {v_remote}")
def pack_project(c, ctx): def _tar_filter(info): if "__pycache__" in info.name: return None return info with Guard("· packing..."): commit = ctx.get("SHORT_COMMIT_HASH") version = ctx.get("LOCAL_VERSION") timestamp = ctx.get("TIMESTAMP") date = datetime.fromisoformat(timestamp).strftime("%Y-%m-%d") app_path = ctx.get("APP_PATH") local_app_path = ctx.get("LOCAL_APP_PATH") artifact_name = f"pubpublica--{date}--{version}--{commit}" artifact_ext = ".tar.gz" artifact_file = artifact_name + artifact_ext artifact_dir = os.path.abspath("build/") artifact_path = os.path.join(artifact_dir, artifact_file) ctx.update({"ARTIFACT_ID": artifact_name}) ctx.update({"ARTIFACT_FILE": artifact_file}) ctx.update({"ARTIFACT_LOCAL_PATH": artifact_path}) deploy_path = os.path.join(app_path, artifact_name) ctx.update({"DEPLOY_PATH": deploy_path}) includes = ctx.get("INCLUDES") or [] paths = [os.path.join(local_app_path, i) for i in includes] with tarfile.open(artifact_path, "w:gz") as tar: for name, path in zip(includes, paths): tar.add(path, arcname=name, filter=_tar_filter) md5 = hashlib.md5() block_size = 65536 with open(artifact_path, "rb") as f: while data := f.read(block_size): md5.update(data) ctx.update({"ARTIFACT_MD5": md5.hexdigest()})
def build_context(c): with Guard("· gathering build information..."): config = util.template("pubpublica.json") context = {} context.update(config.get("BUILD", {})) local_config_path = os.path.abspath(context.get("LOCAL_CONFIG_PATH")) context.update({"LOCAL_CONFIG_PATH": local_config_path}) local_app_path = os.path.abspath(context.get("LOCAL_APP_PATH")) context.update({"LOCAL_APP_PATH": local_app_path}) context.update(config.get("PROVISION", {})) context.update(config.get("DEPLOY", {})) if pubpublica_config := config.get("PUBPUBLICA"): context.update({"PUBPUBLICA": pubpublica_config}) if flask_config := config.get("FLASK"): context.update({"FLASK": flask_config})
def transfer_project(c, ctx): with Guard("· transferring..."): local_artifact = ctx.get("ARTIFACT_LOCAL_PATH") if not local_artifact: raise Exception("no artifact to deployed") if not os.path.isfile(local_artifact): raise Exception("artifact to be deployed is not a file") deploy_path = ctx.get("DEPLOY_PATH") if not fs.create_directory(c, deploy_path, sudo=True): raise Exception("unable to create {deploy_path} on server") artifact_file = ctx.get("ARTIFACT_FILE") artifact_path = os.path.join(deploy_path, artifact_file) temp_path = "/tmp" remote_artifact = os.path.join(temp_path, artifact_file) # if transfer fails, an exception is raised c.put(local_artifact, remote=remote_artifact) fs.move(c, remote_artifact, artifact_path, sudo=True)
def restart_service(c, service): with Guard(f"· restarting {service} service..."): if not systemd.restart(c, service, sudo=True): raise GuardWarning(f"Failed to restart the {service} service")
fs.move(c, tmpfile, remote_path, sudo=True) def setup_flask(c, ctx): # TODO: merge with setup_pubpublica? print("setting up flask") local_config_path = ctx.get("LOCAL_CONFIG_PATH") deploy_path = ctx.get("DEPLOY_PATH") if not (cfg := ctx.get("FLASK") or {}): log.warning("unable to locate flask config") if not (template_file := cfg.get("FLASK_CONFIG_FILE")): raise Exception("path to flask config template is not set") with Guard("· building config files..."): if path := cfg.get("FLASK_SECRET_KEY_PATH"): pw = PASS.get(path) cfg.update({"FLASK_SECRET_KEY": pw}) cfg.pop("FLASK_SECRET_KEY_PATH", None) config_path = ctx.get("LOCAL_CONFIG_PATH") template_path = os.path.join(config_path, template_file) remote_path = os.path.join(deploy_path, template_file) render_and_upload(c, template_path, remote_path, cfg) def setup_redis(c, ctx): print("setting up redis") local_config_path = ctx.get("LOCAL_CONFIG_PATH") deploy_path = ctx.get("DEPLOY_PATH")