vade-cli 0.1.0

A command-line tool to deploy applications on Linux servers
{% include "shared/header.py.j2" %}
{% include "shared/create-tasks.py.j2" %}

{%- if vade.internal.app.paths.systemd_units %}
###
# Sanity checks
###

{%- for unit in vade.internal.app.paths.systemd_units %}
# This unit must not collide with a pre-existing, unmanaged unit file
# (an existing unit is considered unmanaged if it has no copy in the app's active deployment)
if host.get_fact(File, path="{{ unit.installed_path }}") and not host.get_fact(File, path="{{ unit.active_path }}"):
    raise DeployError(
        "Your application wants to install the systemd unit `{{ unit.name }}`, but "
        "`{{ unit.installed_path }}` already exists and is not managed by vade. "
        "If removing the existing unit is not possible, try using a different app name."
    )
{%- endfor %}
{%- endif %}

###
# Initialize deployment candidate
###

files.directory(
    name="Delete stale candidate deployment if it exists",
    path="{{ vade.internal.app.paths.deployment_candidate }}",
    present=False,
)

files.directory(
    name="Create candidate deployment directory",
    path="{{ vade.internal.app.paths.deployment_candidate }}",
    user="{{ vade.app.username }}",
    group="{{ vade.app.username }}",
    mode="755",
)

{% if vade.internal.app.paths.artifacts_local is defined %}
files.rsync(
    name="Sync local artifacts to candidate deployment",
    src="{{ vade.internal.app.paths.artifacts_local }}/",
    dest="{{ vade.internal.app.paths.artifacts_candidate }}",
    flags=["-a", "--delete", "--link-dest={{ vade.app.paths.artifacts }}"],
    _sudo=True,
    _sudo_user="{{ vade.app.username }}",
)
{% endif %}

{% for unit in vade.internal.app.paths.systemd_units %}
files.put(
    name="Upload systemd unit to candidate deployment",
    src="{{ unit.local_path }}",
    dest="{{ unit.candidate_path }}",
    user="{{ vade.app.username }}",
    group="{{ vade.app.username }}",
    mode="644",
)
{% endfor %}

{% if vade.internal.app.paths.caddyfile_local is defined %}
files.put(
    name="Upload Caddyfile to candidate deployment",
    src="{{ vade.internal.app.paths.caddyfile_local }}",
    dest="{{ vade.internal.app.paths.caddyfile_candidate }}",
    user="{{ vade.app.username }}",
    group="{{ vade.app.username }}",
    mode="644",
)
{% endif %}

###
# Assign ports and bake them into the candidate's files (if necessary)
###
if not host.get_fact(Which, command="python3"):
    raise DeployError(
        "vade needs `python3` on the target host to assign ports for `{{ vade.app.name }}`, "
        "but it was not found. Please install python3 and try again."
    )

server.shell(
    name="Assign ports and substitute them into the candidate's templated files",
    commands=[
        "python3 {{ vade.internal.assign_ports_script.remote }}"
        " {{ vade.internal.app.paths.deployment_active }}/assigned-ports"
        " {{ vade.internal.app.paths.deployment_candidate }}"
        " {{ vade.app.username }}"
        {%- if vade.internal.app.paths.caddyfile_candidate is defined %}
        " {{ vade.internal.app.paths.caddyfile_candidate }}"
        {%- endif %}
        {%- for unit in vade.internal.app.paths.systemd_units %}
        " {{ unit.candidate_path }}"
        {%- endfor %}
    ],
)

files.directory(
    name="Delete previous deployment",
    path="{{ vade.internal.app.paths.deployment_previous }}",
    present=False,
)

###
# Promote deployment candidate to active deployment
###

server.shell(
    name="Promote deployment candidate (rolling back on failure)",
    commands=r"""
# The weird structure of the script is actually a poor man's try/catch in bash
set -uo pipefail

bash <<'__VADE_PROMOTE__'
set -euo pipefail
{% with src_dir=vade.internal.app.paths.deployment_candidate, downgraded_dir=vade.internal.app.paths.deployment_previous %}{% include "deploy-promote.sh.j2" %}{% endwith %}
__VADE_PROMOTE__
promote_status=$?

if [ "$promote_status" -eq 0 ]; then
  exit 0
fi

echo "Promotion failed (exit $promote_status), rolling back to the previous deployment..." >&2
bash <<'__VADE_ROLLBACK__'
set -euo pipefail
{% with src_dir=vade.internal.app.paths.deployment_previous %}{% include "deploy-promote.sh.j2" %}{% endwith %}
__VADE_ROLLBACK__
rollback_status=$?

if [ "$rollback_status" -ne 0 ]; then
  echo "FATAL: rollback failed (exit $rollback_status). The host may be in an inconsistent state and needs manual recovery." >&2
  exit 2
fi

echo "Deploying failed and was rolled back" >&2
exit 1
""",
)