{% extends "base_template.html" %} {% block title %}{{ _('Deployments') }} - {{ _('Admin') }}{% endblock %} {% block head %} {% if refresh_seconds %}{% endif %} {% endblock %} {% block breadcrumb %} {% endblock %} {% block content %}

{{ _('Deployments') }}

{{ _('This site follows a release channel. When a new release is published, the deployment agent backs up the database, replaces the web container and checks that the new version starts. If it does not, the agent puts the previous version and its database back.') }}

{% with messages = get_flashed_messages(with_categories=true) %} {% for category, message in messages %} {% endfor %} {% endwith %} {% if connection != 'ok' %} {# The agent is missing, stopped or misconfigured. The site itself is fine, so this explains rather than alarms. #}
{% if connection == 'not_configured' %}

{{ _('Automatic deployment is not set up in this environment') }}

{{ _('The deployment agent only runs in production, next to the web container. There, splent product:deploy generates the shared secret and starts the agent.') }}

{{ _('To turn it on, set SPLENT_IMAGE in the product .env.deploy to the release channel the server should follow, for example %(example)s, and deploy again.', example='git.splent.io/splent-io/' ~ (SPLENT_APP or 'my_product') ~ ':stable') }}

{% elif connection == 'unreachable' %}

{{ _('The deployment agent cannot be reached right now') }}

{{ _('The site keeps running as usual. Only automatic updates are affected until the agent answers again.') }}

{{ _('Check that the agent container is running next to the web container. This page looks for it at %(url)s.', url=agent_url) }}

{% elif connection == 'refused' %}

{{ _('The deployment agent did not accept this page') }}

{{ _('AUTODEPLOY_SECRET must be the same for the web container and the agent. Run splent product:deploy again so both read it from the same .env.deploy.') }}

{% else %}

{{ _('The deployment agent gave an answer this page cannot read') }}

{{ _('The agent and this feature may be of different versions. Deploying the product again brings them together.') }}

{% endif %} {% if error %}

{{ error }}

{% endif %} {% if configured_image or configured_window %}
{% if configured_image %}
{{ _('Release channel') }}
{{ configured_image }}
{% endif %} {% if configured_window %}
{{ _('Update window') }}
{{ _('%(window)s UTC', window=configured_window) }}
{% endif %}
{% endif %}
{% else %} {% if status.last_rolled_back and status.state != 'rolling_back' %} {% endif %}
{{ status.label }} {% if status.paused and status.state != 'paused' %} {{ _('Paused') }} {% endif %}

{{ status.message }}

{% if status.detail %}

{{ status.detail }}

{% endif %} {% if status.problems %} {% endif %}
{{ _('Release channel') }}
{% if status.image %}{{ status.image }}{% else %}{{ _('Not set') }}{% endif %}
{{ _('Running image') }}
{% if status.running_image %}{{ status.running_image }}{% else %}{{ _('Unknown') }}{% endif %}
{{ _('Product version') }}
{{ status.product_version or _('Unknown') }}
{% if status.pending_image %}
{{ _('Newest release') }}
{{ status.pending_image }}
{% endif %}
{{ _('Last check') }}
{{ status.last_check or _('Not yet') }}{% if status.interval_minutes %} ({{ ngettext('checks every minute', 'checks every %(num)d minutes', status.interval_minutes) }}){% endif %}
{{ _('Update window') }}
{% if status.window %}{{ _('%(window)s UTC', window=status.window) }}{% else %}{{ _('Any time') }}{% endif %}
{{ _('Automatic updates') }}
{% if status.paused %}{{ _('Paused') }}{% else %}{{ _('On') }}{% endif %}
{{ action_form.hidden_tag() }}
{% if status.paused %}
{{ action_form.hidden_tag() }}
{% else %}
{{ action_form.hidden_tag() }}
{% endif %}

{{ _('Check now looks for a new release right away and deploys it, even outside the update window. A release that failed before is never deployed again.') }}

{{ _('History') }}

{% if history %}
{% for entry in history %} {% endfor %}
{{ _('When') }} {{ _('Change') }} {{ _('Features updated') }} {{ _('Result') }} {{ _('Duration') }}
{{ entry.time }} {{ entry.from_image or '?' }} → {{ entry.to_image or '?' }} {% if entry.product_version %}
{{ _('Product %(version)s', version=entry.product_version) }}
{% endif %} {% if entry.framework %}
{{ _('Framework %(version)s', version=entry.framework) }}
{% endif %}
{% if entry.features %}
    {% for feature in entry.features %}
  • {{ feature.name }} {{ feature.change }}
  • {% endfor %}
{% else %} {{ _('None') }} {% endif %}
{{ entry.result_label }} {% if entry.reason %}
{{ entry.reason }}
{% endif %} {% for problem in entry.problems %}
{{ problem }}
{% endfor %} {% if entry.log_tail %}
{{ _('Last log lines of the new version') }}
{{ entry.log_tail }}
{% endif %}
{{ entry.duration }}
{% else %}
{{ _('No deploys yet. The first one happens when the channel points to a release this server does not run.') }}
{% endif %}
{{ _('Restoring a backup by hand') }}

{{ _('The agent backs up the database before every deploy and keeps the newest ones in its data volume. Each file is named after the time it was taken and the image the database belonged to.') }}

{% if status.backups %} {% endif %}

{{ _('A failed deploy is rolled back with its backup automatically. There is no button to roll back a deploy that worked, because restoring a backup later throws away everything written since. If you need to, do it on the server, after pausing automatic updates.') }}

  1. {{ _('Copy the backup out of the agent container.') }}
    docker cp {{ agent_host or 'PRODUCT_autodeploy' }}:/data/backups/BACKUP.sql.gz .
  2. {{ _('Stop the web container, so nothing writes while the database is replaced.') }}
    docker stop {{ status.web_container or 'PRODUCT_web_deploy' }}
  3. {{ _('Drop and recreate the database, then import the backup.') }}
    docker exec {{ status.db_container or 'PRODUCT_db_deploy' }} bash -c 'MYSQL_PWD="$MARIADB_ROOT_PASSWORD" mariadb -uroot -e "DROP DATABASE IF EXISTS \`$MARIADB_DATABASE\`; CREATE DATABASE \`$MARIADB_DATABASE\`"'
    gunzip -c BACKUP.sql.gz | docker exec -i {{ status.db_container or 'PRODUCT_db_deploy' }} bash -c 'MYSQL_PWD="$MARIADB_ROOT_PASSWORD" mariadb -uroot "$MARIADB_DATABASE"'
  4. {{ _('Start a web container on the image named in the backup file, so code and database match, then resume automatic updates.') }}
{% if status.agent_version %}

{{ _('Deployment agent %(version)s', version=status.agent_version) }}

{% endif %} {% endif %} {% endblock %}