ansible/roles/awx/tasks/awx2_deploy.yml

693 lines
20 KiB
YAML
Raw Normal View History

2021-06-10 14:41:21 +02:00
---
#
# 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 postrgres database data files
#
- name: create directory {{ pg_volume_datapath | default(awx_composedir + '/postgres/data') }}
file:
path: "{{ pg_volume_datapath | default(awx_composedir + '/postgres/data') }}"
state: directory
owner: "{{ awx_user }}"
group: "{{awx_group }}"
mode: '0750'
2021-06-10 14:41:21 +02:00
#
# 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"
...