{# Copyright 2020 Curtin University
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Author: James Diprose -#}
version: '3.8'

{# Values same for different environments #}
{%- set postgres_user="observatory" -%}
{%- set airflow_db_name="airflow" -%}
{%- set postgres_password="${POSTGRES_PASSWORD}" -%}
{%- set airflow_ui_user_email="${AIRFLOW_UI_USER_EMAIL}" -%}
{%- set airflow_ui_user_password="${AIRFLOW_UI_USER_PASSWORD}" -%}

{# Values different in different environments #}
{%- if config.backend.type.value == 'local' -%}

{%- set host_data_path="${HOST_OBSERVATORY_HOME}/data" -%}
{%- set host_logs_path="${HOST_OBSERVATORY_HOME}/logs" -%}
{%- set host_postgres_path="${HOST_OBSERVATORY_HOME}/postgres" -%}
{%- set google_application_credentials="${HOST_GOOGLE_APPLICATION_CREDENTIALS}" -%}
{%- set postgres_hostname="postgres" -%}
{%- set redis_hostname="redis" -%}
{%- set api_server_db="observatory" -%}
{%- set api_server_port="5002" -%}
{%- set api_uri="http://apiserver:5002" -%}
{%- else -%}

{%- set host_data_path="/opt/observatory/data" -%}
{%- set host_logs_path="/opt/airflow/logs" -%}
{%- set google_application_credentials="/opt/observatory/google_application_credentials.json" -%}
{%- set postgres_hostname="${POSTGRES_HOSTNAME}" -%}
{%- set redis_hostname="${REDIS_HOSTNAME}" -%}
{%- set api_uri="${API_URI}" -%}

{%- endif -%}

{# Local settings -#}
x-environment: &environment
  # Airflow settings
  AIRFLOW__CORE__EXECUTOR: CeleryExecutor
  AIRFLOW__CORE__SQL_ALCHEMY_CONN: "postgresql+psycopg2://{{ postgres_user }}:{{ postgres_password }}@{{ postgres_hostname }}:5432/{{ airflow_db_name }}"
  AIRFLOW__CORE__FERNET_KEY: ${AIRFLOW_FERNET_KEY}
  AIRFLOW__CORE__LOAD_EXAMPLES: "False"
  AIRFLOW__CORE__LOAD_DEFAULT_CONNECTIONS: "False"
  AIRFLOW__CORE__EXECUTE_TASKS_NEW_PYTHON_INTERPRETER: "True"
  AIRFLOW__WEBSERVER__RBAC: "True"
  AIRFLOW__WEBSERVER__SECRET_KEY: ${AIRFLOW_SECRET_KEY}
  AIRFLOW__CELERY__BROKER_URL: "redis://:@{{ redis_hostname }}:6379/0"
  AIRFLOW__CELERY__RESULT_BACKEND: "db+postgresql://{{ postgres_user }}:{{ postgres_password }}@{{ postgres_hostname }}:5432/{{ airflow_db_name }}"
  AIRFLOW__CELERY_BROKER_TRANSPORT_OPTIONS__VISIBILITY_TIMEOUT: 259200
  AIRFLOW__API__AUTH_BACKEND: airflow.api.auth.backend.basic_auth
  {%- if config.backend.type.value == 'terraform' %}
  AIRFLOW__LOGGING__REMOTE_LOGGING: "True"
  AIRFLOW__LOGGING__REMOTE_BASE_LOG_FOLDER: "gs://${AIRFLOW_VAR_AIRFLOW_BUCKET}/logs"
  AIRFLOW__LOGGING__REMOTE_LOG_CONN_ID: "google_cloud_observatory"
  AIRFLOW__SECRETS__BACKEND: "airflow.providers.google.cloud.secrets.secret_manager.CloudSecretManagerBackend"
  AIRFLOW__SECRETS__BACKEND_KWARGS: "{'connections_prefix': 'airflow-connections', 'variables_prefix': 'airflow-variables', 'sep': '-'}"
  {%- endif %}
  # Set Airflow DAGs folder to installed observatory platform dags
  {%- if config.observatory.package_type != 'editable' %}
  AIRFLOW__CORE__DAGS_FOLDER: "/home/airflow/.local/lib/python3.8/site-packages/observatory/platform/dags"
  {%- endif %}

  # Paths to google credentials and UI user settings
  GOOGLE_APPLICATION_CREDENTIALS: "/run/secrets/google_application_credentials.json"
  AIRFLOW_UI_USER_EMAIL: "{{ airflow_ui_user_email }}"
  AIRFLOW_UI_USER_PASSWORD: "{{ airflow_ui_user_password }}"

  {%- if config.backend.type.value == 'local' %}
  # Connections
  {%- for conn in config.airflow_connections %}
  {{ conn.conn_name }}: {{ '${' + conn.conn_name + '}' }}
  {%- endfor %}

  {%- endif %}

  # Variables
  AIRFLOW_VAR_DATA_PATH: "/opt/observatory/data"
  AIRFLOW_VAR_DAGS_MODULE_NAMES: {{ config.dags_module_names  }}
  {%- for var in config.make_airflow_variables() %}
  {%- if config.backend.type.value == 'local' %}
  {{ var.env_var_name }}: '{{ var.value }}'
  {%- else %}
  {{ var.env_var_name }}: {{ '${' + var.env_var_name + '}' }}
  {%- endif %}
  {%- endfor %}

x-volumes: &volumes
  - "{{ host_logs_path }}:/opt/airflow/logs"
  - "{{ host_data_path }}:/opt/observatory/data"

  # Volume mapping when observatory installed in editable mode
  {%- if config.backend.type.value == 'local' and config.observatory.package_type == 'editable' %}
  - "{{ config.observatory.host_package }}/observatory/platform/dags:/opt/airflow/dags"
  {%- elif config.backend.type.value == 'terraform' and config.observatory.package_type == 'editable'  %}
  - "/opt/observatory-platform/observatory/platform/dags:/opt/airflow/dags"
  {%- endif -%}

  # Volume mappings for Python packages, incl observatory-platform and dags projects:
  {%- for package in config.python_packages %}
  {%- if config.backend.type.value == 'local' and package.type == 'editable' %}
  - "{{ package.host_package }}:/opt/{{ package.name }}"
  {%- elif config.backend.type.value == 'terraform' and package.type == 'editable'  %}
  - "/opt/{{ package.name }}:/opt/{{ package.name }}"
  {%- endif -%}
  {%- endfor %}

{% if config.backend.type.value == 'local' %}
{# Local network #}
x-network-mode: &networks
  networks:
    - {{ config.observatory.docker_network_name }}

{%- else %}

{#- Cloud network -#}
x-network-mode: &networks
  network_mode: "host"

{%- endif -%}

{%- if config.backend.type.value == 'terraform' or not config.observatory.docker_network_is_external %}
{# Cloud network or not external network #}
networks:
  {{ config.observatory.docker_network_name }}:
    name: {{ config.observatory.docker_network_name }}
    driver: bridge
{%- else %}
networks:
  {{ config.observatory.docker_network_name }}:
    external: true
{%- endif %}

x-build: &build
  context: .
  dockerfile: Dockerfile.observatory
  args:
    - HOST_USER_ID=${HOST_USER_ID}

services:
  redis:
    image: redis:latest
    restart: always
    ports:
      - ${HOST_REDIS_PORT}:6379
    networks:
      - {{ config.observatory.docker_network_name }}
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 30s
      retries: 50

  flower:
    build: *build
    environment: *environment
    volumes: *volumes
    restart: always
    networks:
      - {{ config.observatory.docker_network_name }}
    depends_on:
      redis:
        condition: service_healthy
      {% if config.backend.type.value == 'local' -%}
      postgres:
        condition: service_healthy
      {%- endif %}
    ports:
      - ${HOST_FLOWER_UI_PORT}:5555
    command: celery flower
    healthcheck:
      test: ["CMD", "curl", "--fail", "http://localhost:5555/"]
      interval: 10s
      timeout: 10s
      retries: 5
    {% if (config.google_cloud is not none) and (config.google_cloud.credentials is not none) -%}
    secrets:
      - google_application_credentials.json
    {%- endif %}

  webserver:
    build: *build
    environment: *environment
    volumes: *volumes
    restart: always
    networks:
      - {{ config.observatory.docker_network_name }}
    depends_on:
      redis:
        condition: service_healthy
      {% if config.backend.type.value == 'local' -%}
      postgres:
        condition: service_healthy
      {%- endif %}
    {% if (config.google_cloud is not none) and (config.google_cloud.credentials is not none) -%}
    secrets:
      - google_application_credentials.json
    {%- endif %}
    ports:
      - ${HOST_AIRFLOW_UI_PORT}:8080
    command: webserver
    healthcheck:
      test: ["CMD-SHELL", "[ -f /opt/airflow/airflow-webserver.pid ]"]
      interval: 30s
      timeout: 30s
      retries: 30

  scheduler:
    build: *build
    environment: *environment
    volumes: *volumes
    restart: always
    networks:
      - {{ config.observatory.docker_network_name }}
    depends_on:
      webserver:
        condition: service_healthy
    {% if (config.google_cloud is not none) and (config.google_cloud.credentials is not none) -%}
    secrets:
      - google_application_credentials.json
    {%- endif %}
    command: scheduler

  worker_local:
    build: *build
    environment: *environment
    volumes: *volumes
    restart: always
    <<: *networks
    depends_on:
      - scheduler
    {% if (config.google_cloud is not none) and (config.google_cloud.credentials is not none) -%}
    secrets:
      - google_application_credentials.json
    {%- endif %}
    command: celery worker -q default

  worker_remote:
    build: *build
    environment: *environment
    volumes: *volumes
    restart: always
    <<: *networks
    {% if (config.google_cloud is not none) and (config.google_cloud.credentials is not none) -%}
    secrets:
      - google_application_credentials.json
    {%- endif %}
    command: celery worker -q remote_queue

  {% if config.backend.type.value == 'local' -%}
  {% if config.observatory.enable_elk -%}
  elasticsearch:
    image: "docker.elastic.co/elasticsearch/elasticsearch:8.2.2"
    restart: always
    volumes:
      - "{{ host_data_path }}/elastic:/usr/share/elasticsearch/data"
    ports:
      - ${HOST_ELASTIC_PORT}:9200
    networks:
      - {{ config.observatory.docker_network_name }}
    environment:
      - cluster.name="observatory-cluster"
      - discovery.type=single-node
      - network.host=0.0.0.0
      - xpack.security.enabled=false

  kibana:
    image: "docker.elastic.co/kibana/kibana:8.2.2"
    restart: always
    ports:
      - ${HOST_KIBANA_PORT}:5601
    networks:
      - {{ config.observatory.docker_network_name }}
    environment:
       - xpack.security.enabled=false
  {%- endif %}

  postgres:
    image: postgres:12.2
    environment:
      - POSTGRES_DB={{ airflow_db_name }}
      - POSTGRES_USER={{ postgres_user }}
      - POSTGRES_PASSWORD={{ postgres_password }}
    volumes:
      - {{ host_postgres_path }}:/var/lib/postgresql/data
    restart: always
    networks:
      - {{ config.observatory.docker_network_name }}
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "{{ postgres_user }}", "-d", "{{ airflow_db_name }}"]
      interval: 5s
      retries: 5

  apiserver:
    build:
      context: .
      dockerfile: Dockerfile.apiserver
      args:
        - HOST_USER_ID=${HOST_USER_ID}
    environment:
      - API_SERVER_DB={{api_server_db}}
      - BASE_DB_URI=postgresql://{{postgres_user}}:{{postgres_password}}@{{postgres_hostname}}/postgres
      - OBSERVATORY_DB_URI=postgresql://{{postgres_user}}:{{postgres_password}}@{{postgres_hostname}}/{{api_server_db}}
      - OBSERVATORY_API_PORT={{api_server_port}}
    volumes: *volumes
    restart: always
    <<: *networks
    depends_on:
      postgres:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "nc", "-z", "-v", "apiserver", "{{api_server_port}}"]
      interval: 30s
      retries: 20
    command: apiserver
    ports:
      - ${HOST_API_PORT}:{{api_server_port}}
    entrypoint: /entrypoint-api.sh
  {%- endif %}

  seed_db:
    build:
      context: .
      dockerfile: Dockerfile.seed_db
      args:
        - HOST_USER_ID=${HOST_USER_ID}
    environment:
      - API_URI={{api_uri}}
    volumes: *volumes
    <<: *networks
    depends_on:
      apiserver:
        condition: service_healthy

{% if (config.google_cloud is not none) and (config.google_cloud.credentials is not none) -%}
secrets:
  google_application_credentials.json:
    file: {{ google_application_credentials }}
{%- endif %}
