---
#
# make sure that group <awx_group> is defined
#
- name: create group {{ awx_group|default('awx') }} if not already present
  group:
    name: "{{ awx_group|default('awx') }}"
    state: present

#
# make sure that user <awx_user> is defined
# (user will be locked if <awx_password> is not defined)
#
- name: create user {{ awx_user|default('awx') }} if not already present
  user:
    name: "{{ awx_user }}"
    shell: /bin/bash
    comment: "AWX User"
    group: "{{ awx_group|default('awx') }}"
    groups: docker
    home: "/home/{{ awx_user }}"
    createhome: yes
    password: "{{ awx_password|default('') | password_hash('sha512') }}"
    password_lock: "{{ awx_password is not defined }}"

#
# Create the base directroy for checking out the AWX repo from Github
#
- name: make sure that directory {{ awx_basedir }} exists
  file:
    path: "{{ awx_basedir }}"
    state: directory
    owner: "{{ awx_user }}"
    group: "{{ awx_group }}"
    mode: '0755'

#
# Checkout the Git Repo from Github using the given AWX version
# This is needed to build the image locally.
#
- name: checkout awx repo version {{ awx_version }} to {{ awx_basedir }}
  git:
    clone: yes
    dest: "{{ awx_basedir | default('/opt/awx') }}/{{ awx_version }}"
    repo: "{{ awx_github_repo }}"
    version: "{{ awx_version }}"
    force: yes
  become: yes
  become_user: "{{ awx_user }}"

#
# Add our proxy for http and https to the Makefile that we just have checked
# out from Github. Otherwise the "make docker-compose-build" which builds the
# local docker image will fail.
#
- name: add build-args to Makefile in {{ awx_basedir }}/{{ awx_version }}
  lineinfile:
    path: "{{ awx_basedir }}/{{ awx_version }}/Makefile"
    line: '            --build-arg http_proxy=${http_proxy} --build-arg https_proxy=${https_proxy} \'
    firstmatch: yes
    insertafter: ".*--build-arg BUILDKIT_INLINE_CACHE.*"
    state: present
  when:
    - build_docker_image is defined
    - build_docker_image|bool == True

#
# run "make docker-compose-build" in the checked out Git Repo context to
# build the docker image.
#
- name: run make docker-compose-build in {{ awx_basedir }}/{{ awx_version }}
  make:
    chdir: "{{ awx_basedir }}/{{ awx_version }}"
    target: docker-compose-build
    params:
      COMPOSE_TAG: "{{ awx_image_tag }}"
      DEVEL_IMAGE_NAME: "{{ awx_image }}:{{ awx_image_tag }}"
  become: yes
  become_user: "{{ awx_user }}"
  when:
    - build_docker_image is defined
    - build_docker_image|bool == True

#
# Push the Docker image to our local registry
#
- name: push image to docker registry
  include_tasks:
    push.yml
  when:
    - push_docker_image is defined
    - push_docker_image|bool == True

#
# create the directory where we will deploy our docker-compose 
# configuration files.
#
- name: make sure that directory {{ awx_composedir }} exists
  file:
    path: "{{ awx_composedir }}"
    state: directory
    owner: "{{ awx_user }}"
    group: "{{ awx_group }}"
    mode: '0750'

#
# create a sub-directory initdb.d to hold the postgres database dump
# in case we want to deploy AWX using an existing database dump.
#
- name: make sure that directory {{ awx_composedir }}/initdb.d exists
  file:
    path: "{{ awx_composedir }}/initdb.d"
    state: directory
    owner: "{{ awx_user }}"
    group: "{{ awx_group }}"
    mode: '0755'
  when:
    - database_dump_file is defined

#
# copy the postgres database dump file to the initdb.d directory
# during initialization of the postgres container, the file will
# be loaded an the database will be populated. 
#
- name: copy database dump file to initialize postgres db
  copy:
    src: "{{ database_dump_file }}"
    dest: "{{ awx_composedir }}/initdb.d/{{ database_dump_file }}"
    owner: "{{ awx_user }}"
    group: "{{ awx_group }}"
    mode: '0644'
  when: database_dump_file is defined

#
# make sure that there is only one single postgres dump file in
# the initdb.d directory. Otherwise the initialization will fail
# because the postgres container will be unable to load two files
# with a similar database definition.
#
# Check if there are other files in the initdb.d directory and register
# list of files in variable obsolete_dump_files
#
- name: check if other files exist in initdb.d directory
  find:
    path: "{{ awx_composedir }}/initdb.d"
    excludes: "{{ database_dump_file|default() }}"
  register: obsolete_dump_files

#
# delete all files from initdb.d directory registered in variable
# named obsolete_dump_files.
#
- name: delete obsolete files from initdb.d directory
  file:
    path: "{{ item.path }}"
    state: absent
  with_items:
    - "{{ obsolete_dump_files.files }}"
  when:
    - obsolete_dump_files.files is defined

#
# create a redis sub-directory under the compose directory
#
- name: Create redis directory
  file:
    path: "{{ awx_composedir }}/redis"
    state: 'directory'
    mode: '0755'
    owner: "{{ awx_user }}"
    group: "{{ awx_group }}"

#
# create a sub-directory to store our secrets
#
- name: Create secrets directory
  file:
    path: "{{ awx_composedir }}/secrets"
    state: 'directory'
    mode: '0750'
    owner: "{{ awx_user }}"
    group: "{{ awx_group }}"

#
# check if secrets are already available in secret sub-directory
# under the docker-compose directory
#
- name: Detect secrets
  stat:
    path: "{{ awx_composedir }}/secrets/{{ item }}.yml"
  register: secrets
  when: not lookup('vars', item, default='')
  loop:
    - pg_password
    - secret_key
    - broadcast_websocket_secret

#
# set variables based on the secrets
#
- name: set variables from secrets if needed
  set_fact:
    '{{ item }}': "{{ lookup('vars', item, default='') or lookup('password', '/dev/null chars=ascii_letters') }}"
  when: not lookup('vars', item, default='')
  loop:
    - pg_password
    - secret_key
    - broadcast_websocket_secret
   
#
# write variables to secret files in secret sub-directory
# under the docker-compose directory
#
- name: Generate secrets if needed
  template:
    src: 'secrets.yml.j2'
    dest: '{{ awx_composedir }}/secrets/{{ item.item }}.yml'
    mode: '0640'
    owner: "{{ awx_user }}"
    group: "{{ awx_group }}"
  when: not lookup('vars', item.item, default='') and not item.stat.exists
  loop: "{{ secrets.results }}"

#
# include vars from secret files if they are not explicitly defined
#
- name: Include generated secrets unless they are explicitly passed in
  include_vars: "secrets/{{ item.item }}.yml"
  #no_log: true
  when: not lookup('vars', item.item, default='')
  loop: "{{ secrets.results }}"

#
# write the SECRET_KEY file in the docker-compose directory
#
- name: Write out SECRET_KEY
  copy:
    content: "{{ secret_key }}"
    dest: "{{ awx_composedir }}/SECRET_KEY"
    owner: "{{ awx_user }}"
    group: "{{ awx_group }}"
    mode: '0640'
  no_log: true

#
# deploy configuration files to the docker-compose directory
#
- name: deploy configuration files
  template:
    src: "{{ item }}.j2"
    dest: "{{ awx_composedir }}/{{ item }}"
    mode: '0640'
    owner: "{{ awx_user }}"
    group: "{{ awx_group }}"
  register: deploy_awx_cfg
  with_items:
    - "database.py"
    - "websocket_secret.py"

#
# deploy haproxy.cfg to the docker-compose directory
# if the cluster_node_count is greater than 1
# (default=1, haproxy is only deployed if cluster_node_count > 1)
#
- name: deploy configuration files
  template:
    src: "haproxy.cfg.j2"
    dest: "{{ awx_composedir }}/haproxy.cfg"
    mode: '0640'
    owner: "{{ awx_user }}"
    group: "{{ awx_group }}"
  register: deploy_ha_cfg
  when: cluster_node_count|default(1)|int > 1

#
# remove haproxy.cfg from docker-compose directory if
# there is any leftover from previous deployments. File
# will be removed if cluster_node_count <= 1 (default=1).
#
- name: remove haproxy.cfg if no haproxy is deployed
  file:
    path: "{{ awx_composedir }}/haproxy.cfg"
    state: absent
  when: cluster_node_count|default(1)|int <= 1

#
# copy file local_settings.py to docker-compose directory
#
- name: Copy local_settings.py
  copy:
    src: "local_settings.py"
    dest: "{{ awx_composedir }}/local_settings.py"
    owner: "{{ awx_user }}"
    group: "{{ awx_group }}"
    mode: '0640'

#
# get some information about the OS from the docker system
#
- name: Get OS info for sdb
  shell: |
    docker info | grep 'Operating System'
  register: os_info
  changed_when: false

#
# Get the UID of the user which will run the AWX container.
# The AWX container will not run as root. The user is defined
# using the variable awx_user.
#
- name: Get user UID
  shell: "id -u {{ awx_user }}"
  register: awx_user_id
  changed_when: false

#
# set a variable user_id based on the output from the previous command
# this will be used in the docker-compose.yml template
#
- name: Set fact with user UID
  set_fact:
    user_id: "{{ awx_user_id.stdout|int }}"

#
# set "awx_image_tag" variable from the VERSION file in the Git Repo if 
# no "awx_image_tag" is defined.
#
- name: Set global version if not provided
  set_fact:
    awx_image_tag: "{{ lookup('file', awx_basedir + '/' + awx_version + '/VERSION') }}"
  when: awx_image_tag is not defined

#
# copy the supervisor.conf to the docker-compose directory
#
- name: deploy supervisor.conf
  copy:
   src: supervisor.conf
   dest: "{{ awx_composedir }}/supervisor.conf"
   owner: "{{ awx_user }}"
   group: "{{ awx_group }}"

#
# copy the receptor.conf to the docker-compose directory
#
- name: deploy receptor.conf
  copy:
   src: receptor.conf
   dest: "{{ awx_composedir }}/receptor.conf"
   owner: "{{ awx_user }}"
   group: "{{ awx_group }}"

#
# copy the redis.conf to the redis sub-directory in
# the docker-compose directory
#
- name: deploy redis.conf
  copy:
   src: redis.conf
   dest: "{{ awx_composedir }}/redis/redis.conf"
   owner: "{{ awx_user }}"
   group: "{{ awx_group }}"
   mode: '0640'

#
# create docker-compose.yml in the docker-compose directory
# from a template
#
- name: deploy docker-compose.yml
  template:
    src: docker-compose.yml.j2
    dest: "{{ awx_composedir }}/{{ awx_compose_name }}"
    owner: "{{ awx_user }}"
    group: "{{ awx_group }}"
    mode: '0644'

#
# create docker-compose.override.yml in the docker-compose directory
# from a template
#
- name: deploy docker-compose.override.yml
  template:
    src: docker-compose.override.yml.j2
    dest: "{{ awx_composedir }}/{{ awx_compose_override_name }}"
    owner: "{{ awx_user }}"
    group: "{{ awx_group }}"
    mode: '0644'

#
# Create directory to store the database dumps
#
- name: create directory {{ pg_database_dumpdir | default('/var/lib/pgdocker/backup_'+awx_version) }}
  file:
    # path: "{{ pg_database_dumpdir }}"
    path: "{{ pg_database_dumpdir | default('/var/lib/pgdocker/backup_'+awx_version) }}"
    state: directory
    owner: "{{ awx_user }}"
    group: "{{awx_group }}"
    mode: '0750'

#
# deploy pg_dump.sh to dump the postgres database to a gzipped sql
# file. 
#
- name: deploy backup script pg_dump.sh
  template:
    src: pg_dump.sh.j2
    dest: "{{ awx_composedir }}/pg_dump.sh"
    owner: "{{ awx_user }}"
    group: "{{ awx_group }}"
    mode: '0750'

#
# deploy HouseKeeping.sh to remove files older than x days
#
- name: deploy maintainance script HouseKeeping.sh
  copy:
    src: HouseKeeping.sh
    dest: "{{ awx_composedir }}/HouseKeeping.sh"
    owner: "{{ awx_user }}"
    group: "{{ awx_group }}"
    mode: '0750'

#
# install a cron entry to run the postgres backup daily at 23:00h
#
- name: create a cron file under /etc/cron.d for postgres backup
  cron:
    name: "postgres backup for awx ({{ item.tag }})"
    weekday: "*"
    minute: "{{ item.mm }}"
    hour: "{{ item.hh }}"
    user: "{{ awx_user }}"
    job: "{{ awx_composedir }}/pg_dump.sh"
    #cron_file: ansible_awx-postgres-backup
  with_items:
    - { tag: "version {{ awx_version }} #1", hh: 23, mm: 00 }
    - { tag: "version {{ awx_version }} #2", hh: 05, mm: 00 }

#
# start the postgres container from the new created AWX deployment
# This will either create a fresh awx database if no database_dump_file is
# present in the initdb.d sub-directory or it will process the dump file and
# load the data in the file.
#
- name: bring up awx docker-compose project (postgres only)
  docker_compose:
    project_src: "{{ awx_composedir }}"
    services: postgres
    state: present
    files: [ 'docker-compose.yml', 'docker-compose.override.yml' ]
  register: compose_output

#
# check logs from postgres container to see if there is a message indicating that
# a previous database already exists. This can be the case if the awx_db volume 
# exists before deploying this instance of AWX.
#
- name: check if postgres already has an existing db and skips init process
  shell:
    cmd: "docker logs {{ awx_prefix }}_postgres_1"
  register: skip_db_init

#
# run "docker logs <postgres container>" every 5sec until the message
# "PostgreSQL init process complete" is found. This indicates that the 
# loading of the dump file is complete and postgres is ready for client
# connections. This is only performed if the postgres container has
# not skipped the initialization sequence.
#
- name: wait for postgres to finish loading the dump
  shell:
    cmd: "docker logs {{ awx_prefix }}_postgres_1"
  register: wait_for_initdb
  until: wait_for_initdb.stdout is search('PostgreSQL init process complete')
  retries: 30
  delay: 5
  when: not skip_db_init.stdout is search('Skipping initialization')

#
# Wait another 10s to ensure that postgres is available because it will restart
# at the end of the initialization sequence.
#
- name: wait another 10sec ...
  pause:
    seconds: 10
  when: not skip_db_init.stdout is search('Skipping initialization')

#
# set docker_restart to true if either "deploy_awx_cfg.changed" is true
# or "deploy_ha_cfg.changed" is true
#
- name: set variable docker_restart depending on config file changes
  set_fact:
    docker_restart: "{{ deploy_awx_cfg.changed|default(false) or deploy_ha_cfg.changed|default(false) }}"
#
# bring up the remaining containers for the AWX deployment. These
# are redis and awx. The number depends on the variable cluster_node_count.
# If cluster_node_count is greater than 1, a haproxy container is also launched (default=1).
# Containers are restarted if one of the config files has changed.
#
- name: bring up awx docker-compose project
  docker_compose:
    project_src: "{{ awx_composedir }}"
    state: present
    restarted: "{{ docker_restart }}"
    remove_orphans: true
    files: [ 'docker-compose.yml', 'docker-compose.override.yml' ]
  register: compose_output

#
# The initialization of the awx container may take a while. If a database
# dump is loaded from an older version of AWX, the database migration is 
# performed automatically. We will wait for the message "supervisord started"
# in the log of the awx_1 container as an indication that the migration
# and initialization is complete.
#
- name: wait for awx initialization to complete
  shell:
    cmd: "docker logs {{ awx_prefix }}_awx_1"
  register: wait_for_awx
  until: wait_for_awx.stdout is search('supervisord started')
  retries: 40
  delay: 5

#
# Login to the first awx container and run "make clean-ui ui-devel" to compile the
# AWX User Interface.
#
- name: run make clean-ui ui-devel to build AWX UI in docker container - This will take 5-10min...
  shell:
    cmd: "docker exec {{ awx_prefix }}_awx_1 make clean-ui ui-devel"
  register: build_ui_output
  when:
    - build_ui is defined
    - build_ui|bool == True

#
# show the output from the UI build process
#
- name: show output from build UI
  debug:
    var: build_ui_output
  when:
    - build_ui is defined
    - build_ui|bool == True

#
# When a database dump is loaded, it may contain active schedules. These 
# schedules can be disabled by using direct SQL updates in the awx database.
# To disable the schedules, the flag "disable_schedule" must be set to true.
#
- name: disable all awx schedules in database
  shell:
    cmd: |
      docker exec -i {{ awx_prefix }}_postgres_1 psql --user {{ pg_username }} --dbname {{ pg_database }} -t -A <<-EOT
      UPDATE main_schedule SET enabled='f', next_run=NULL
      WHERE enabled='t';
      EOT
  register: disable_schedule_output
  when:
    - disable_schedule|default(false)
    - database_dump_file is defined

#
# show the output from previous update on the awx schedules
#
- name: show output from disable awx schedules
  debug:
    var: disable_schedule_output.stdout
  when:
    - disable_schedule|default(false)
    - database_dump_file is defined

#
# if a database dump is loaded, there might be an old instance
# in a table called "main_instance" in the database. we will 
# remove these "stale" instances from the table based on the
# field "version" which must be different from the awx_version
# that we are currently deploying.
#
# Even after the container seems to have completed the initialization,
# the database table main_instance still contains rows where the version
# field is empty.
#
# check until all rows have a valid version in table main_instance
#
- name: check if all rows have a valid version in main_instance
  shell:
    cmd: |
      docker exec -i {{ awx_prefix }}_postgres_1 psql --user {{ pg_username }} --dbname {{ pg_database }} -t -A <<-EOT
      SELECT hostname FROM main_instance
      WHERE version = '';
      EOT
  register: no_version
  until: no_version.stdout_lines|length == 0
  retries: 12
  delay: 5
  when: database_dump_file is defined

#
# get list of hostnames from instances with an older awx version
#
- name: check if old instance exists in awx db
  shell:
    cmd: |
      docker exec -i {{ awx_prefix }}_postgres_1 psql --user {{ pg_username }} --dbname {{ pg_database }} -t -A <<-EOT
      SELECT hostname FROM main_instance
      WHERE version != '{{ awx_version }}';
      EOT
  register: db_output
  when: database_dump_file is defined

#
# show result from SQL command above containing the hostnames to delete
#
- name: show output from DB
  debug:
    var: db_output.stdout_lines
  when:
    - database_dump_file is defined
    - db_output.stdout

#
# obsolete instances will be removed from the awx deployment using
# command "awx-manage deprovision_instance --hostname <host>" using
# all hosts found in SQL command above.
#
- name: remove old instance from awx if present
  shell:
    cmd: "docker exec -i {{ awx_prefix }}_awx_1 awx-manage deprovision_instance --hostname {{ item }}"
  register: remove_instance_output
  with_items:
    - "{{ db_output.stdout_lines }}"
  when:
    - database_dump_file is defined
    - db_output.stdout_lines is defined

#
# show result from awx-manage command above 
#
- name: show output from awx-manage
  debug:
    var: remove_instance_output
  when: remove_instance_output.stdout is defined

#
# When we create a fresh awx deployment, an admin user is 
# created automatically. The Admin password is written to 
# the log of the awx_1 container. We will monitor the log
# of the container for the message "Admin password".
#
- name: wait for Admin User to be created (when no dump is loaded)
  shell:
    cmd: "docker logs {{ awx_prefix }}_awx_1"
  register: wait_for_admin
  until: wait_for_admin.stdout is search('Admin password')
  retries: 10
  delay: 5
  when: not database_dump_file is defined

#
# set variable admin_password from the output of the awx_1 
# container.
#
- name: retrieve admin password (only for new install)
  set_fact:
    admin_password: "{{ wait_for_admin.stdout_lines|default() |select('match', 'Admin password.+') | list }}"
  when: not database_dump_file is defined

#
# Display some useful information about the just deployed awx
#
- name: display the fresh created password for the admin user (only for new install)
  debug:
    msg: "{{ item }} "
  with_items:
    - "Admin User: admin"
    - "{{ admin_password|default('Admin password: ** not changed - loaded from dump **') }}"
    - "URL: https://{{ ansible_hostname }}.{{ ansible_domain }}:{{ https_port }}/#/login"
...
