--- # # make sure that 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 is defined # (user will be locked if 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' # # 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 " 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 " 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" ...