From e44f7a766c4989efaed0f85971f8c3c2aaefc72f Mon Sep 17 00:00:00 2001 From: David Moreau Simard Date: Tue, 5 Mar 2019 13:38:00 -0500 Subject: [PATCH] Move Ansible role to set up ARA 1.0 from ara-infra Now that ara-{server,clients,plugins} have been merged back into ara, it is simpler and more convenient to have the role in-tree. The role has changed a bit during the import. The most significant change is that the role now defaults to an unprivileged installation for personal use. Change-Id: Ie47fe57356eaa6174d7813b7780a464e16c4a891 --- .zuul.yaml | 35 ++++ playbooks/ara.yaml | 7 + roles/ara/README.rst | 118 ++++++++++++ roles/ara/defaults/main.yaml | 176 ++++++++++++++++++ roles/ara/handlers/main.yaml | 37 ++++ roles/ara/meta/main.yaml | 35 ++++ roles/ara/tasks/config.yaml | 100 ++++++++++ .../django.db.backends.sqlite3.yaml | 30 +++ roles/ara/tasks/install/source.yaml | 35 ++++ roles/ara/tasks/main.yaml | 51 +++++ roles/ara/tasks/pre-requirements.yaml | 82 ++++++++ roles/ara/tasks/web_server/nginx.yaml | 54 ++++++ roles/ara/tasks/wsgi_server/gunicorn.yaml | 44 +++++ roles/ara/templates/ara-api.service.j2 | 21 +++ roles/ara/templates/nginx-ara-api.conf.j2 | 40 ++++ roles/ara/vars/Fedora.yaml | 27 +++ roles/ara/vars/Ubuntu.yaml | 25 +++ tests/integration-post.yaml | 8 +- tests/role-integration-pre.yaml | 35 ++++ 19 files changed, 956 insertions(+), 4 deletions(-) create mode 100644 playbooks/ara.yaml create mode 100644 roles/ara/README.rst create mode 100644 roles/ara/defaults/main.yaml create mode 100644 roles/ara/handlers/main.yaml create mode 100644 roles/ara/meta/main.yaml create mode 100644 roles/ara/tasks/config.yaml create mode 100644 roles/ara/tasks/database_engine/django.db.backends.sqlite3.yaml create mode 100644 roles/ara/tasks/install/source.yaml create mode 100644 roles/ara/tasks/main.yaml create mode 100644 roles/ara/tasks/pre-requirements.yaml create mode 100644 roles/ara/tasks/web_server/nginx.yaml create mode 100644 roles/ara/tasks/wsgi_server/gunicorn.yaml create mode 100644 roles/ara/templates/ara-api.service.j2 create mode 100644 roles/ara/templates/nginx-ara-api.conf.j2 create mode 100644 roles/ara/vars/Fedora.yaml create mode 100644 roles/ara/vars/Ubuntu.yaml create mode 100644 tests/role-integration-pre.yaml diff --git a/.zuul.yaml b/.zuul.yaml index 62149baf..28c6ffad 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -45,6 +45,37 @@ - name: github.com/ansible/ansible override-checkout: stable-2.6 +- job: + name: ara-1.0-role-integration-base + parent: base + files: + - ara/* + - playbooks/* + - roles/* + - tests/* + - .zuul.d/* + - setup.py + - setup.cfg + - requirements.txt + - test-requirements.txt + pre-run: tests/role-integration-pre.yaml + run: playbooks/ara.yaml + vars: + ara_source: "{{ ansible_user_dir }}/src/git.openstack.org/openstack/ara" + ara_web_server: nginx + ara_wsgi_server: gunicorn + ara_www_dir: /var/www/ara + +- job: + name: ara-1.0-role-integration-ubuntu + parent: ara-1.0-role-integration-base + nodeset: ubuntu-bionic + +- job: + name: ara-1.0-role-integration-fedora + parent: ara-1.0-role-integration-base + nodeset: fedora-latest + - project: vars: rtd_webhook_id: '49230' @@ -56,11 +87,15 @@ - ara-1.0-integration-fedora-devel: voting: false - ara-1.0-integration-ubuntu-2.6 + - ara-1.0-role-integration-ubuntu + - ara-1.0-role-integration-fedora - ara-tox-linters - ara-tox-py3 gate: jobs: - ara-1.0-integration-fedora-2.7 - ara-1.0-integration-ubuntu-2.6 + - ara-1.0-role-integration-ubuntu + - ara-1.0-role-integration-fedora - ara-tox-linters - ara-tox-py3 diff --git a/playbooks/ara.yaml b/playbooks/ara.yaml new file mode 100644 index 00000000..669dc297 --- /dev/null +++ b/playbooks/ara.yaml @@ -0,0 +1,7 @@ +- name: Install ARA with default settings + hosts: all + gather_facts: yes + vars: + ansible_python_interpreter: /usr/bin/python3 + roles: + - ara diff --git a/roles/ara/README.rst b/roles/ara/README.rst new file mode 100644 index 00000000..e8d45ca3 --- /dev/null +++ b/roles/ara/README.rst @@ -0,0 +1,118 @@ +ansible-role-ara +================ + +This Ansible role provides a framework for installing one or many instances of +`ara `_ in a variety of opinionated +deployment topologies. + +It is currently tested and supported against Ubuntu 18.04 and Fedora 29. + +Role Variables +============== + +See ``defaults/main.yaml``. + +TL;DR +===== + +Playbook that runs the role with defaults:: + + # Doesn't require superuser privileges + # The API will only be reachable by the offline API client + - name: Install ARA with default settings and no persistent API server + hosts: all + gather_facts: yes + vars: + ansible_python_interpreter: /usr/bin/python3 + roles: + - ara + +What the role ends up doing by default: + +- Installs required packages (``git``, ``virtualenv``, etc.) if superuser privileges are available +- Stores everything in the home directory of the user in ``~/.ara`` +- Retrieves ARA from source +- Installs ARA in a virtualenv +- Generates a random secret key if none are already configured or provided +- Sets up API configuration in ``~/.ara/server/settings.yaml`` +- Runs the API SQL migrations (``ara-manage migrate``) +- Collects static files (``ara-manage collectstatic``) into ``~/.ara/www`` + +About deployment topologies +=========================== + +This Ansible role is designed to support different opinionated topologies that +can be selected with role variables. + +For example, the following role variables are used to provide the topology from +the ``TL;DR`` above: + +- ``ara_install_method: source`` +- ``ara_wsgi_server: null`` +- ``ara_database_engine: django.db.backends.sqlite3`` +- ``ara_web_server: null`` + +The intent is that as the role gains support for other install methods, +wsgi servers, database engines or web servers, it will be possible to +mix and match according to preference or requirements. + +Perhaps ARA could be installed from pypi and run with uwsgi, nginx and mysql. +Or maybe it could be installed from distribution packages and set up to run +with apache, mod_wsgi and postgresql. +Or any combination of any of those. + +Example playbooks +================= + +Install ARA and set up the API to be served by a persistent gunicorn service:: + + # Requires superuser privileges to set up the ara-api service + # The API will be reachable at http://127.0.0.1:8000/api/v1/ + - name: Install ARA and set up the API to be served by gunicorn + hosts: all + gather_facts: yes + vars: + ansible_python_interpreter: /usr/bin/python3 + ara_wsgi_server: gunicorn + ara_wsgi_configure_service: true + roles: + - ara + +Install ARA and set up the API to be served by nginx in front of gunicorn:: + + # Requires superuser privileges to set up nginx and the ara-api service + # The API will be reachable at http://api.ara.example.org + - name: Install ARA and set up the API to be served by nginx in front of gunicorn + hosts: all + gather_facts: yes + vars: + ansible_python_interpreter: /usr/bin/python3 + ara_web_server: nginx + ara_wsgi_server: gunicorn + ara_wsgi_configure_service: true + ara_www_dir: /var/www/ara + ara_api_fqdn: api.ara.example.org + ara_allowed_hosts: + - api.ara.example.org + roles: + - ara + +Copyright +========= + +:: + + Copyright (c) 2019 Red Hat, Inc. + + ARA Records Ansible is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ARA Records Ansible is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ARA Records Ansible. If not, see . diff --git a/roles/ara/defaults/main.yaml b/roles/ara/defaults/main.yaml new file mode 100644 index 00000000..e89992d2 --- /dev/null +++ b/roles/ara/defaults/main.yaml @@ -0,0 +1,176 @@ +--- +# Copyright (c) 2019 Red Hat, Inc. +# +# This file is part of ARA Records Ansible. +# +# ARA Records Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ARA Records Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ARA Records Ansible. If not, see . + +# Root directories in which data, configuration and logs will be stored +ara_root_dir: "{{ ansible_user_dir }}/.ara" # git repos, virtualenvs, sqlite db +ara_log_dir: "{{ ara_root_dir }}/logs" # logs +ara_www_dir: "{{ ara_root_dir }}/www" # django collectstatic assets (html/css/js) + +# Whether or not ara should be installed in a virtual environment. +# Running ara in a virtualenv is recommended to avoid conflicting with +# system-wide python packages. +ara_venv: true + +# When using a virtualenv, location where it will be installed +ara_venv_path: "{{ ara_root_dir }}/virtualenv" + +# How ara will be installed +# source (default): installs from a local or remote git repository specified by ara_source +# pypi (planned): installs from pypi +ara_install_method: source + +# The source where the git repository can be cloned from. +# Can be an URL to a git repository or the path to a local repository on disk. +ara_source: "https://git.openstack.org/openstack/ara" + +# Location where ara will be checked out when installing from source +ara_source_checkout: "{{ ara_root_dir }}/git/ara" + +# Version of ara to install +# This can be a git ref (tag, branch, commit) when installing from source or +# it can be a version number released to PyPi. +# When using "latest" as the source version, HEAD will be used +# When using "latest" as the pypi version, the latest release will be used +ara_install_version: feature/1.0 + +# The web server for serving the ARA API +# It is recommended to specify a web server when deploying a production environment. +# When setting a web server, you'll need to set "ara_www_dir" to a location the +# web server has access to. +# If no web server are specified, the Ansible role will set "ara_debug" to true +# in order to let Django serve the static files for the admin interface and the +# API browser. +# - none (null - default) +# - nginx (recommended) +# - apache (planned) +ara_web_server: null + +# The WSGI server for running ARA's API server +# - none (null - default) +# - gunicorn (recommended) +# - uwsgi (planned) +# - mod_wsgi (planned) +ara_wsgi_server: null + +# Address and port on which the wsgi server will bind +# Changing this value means you might need to adjust "ara_allowed_hosts" and +# "ara_cors_origin_whitelist". +ara_wsgi_bind: "127.0.0.1:8000" + +# When using a web server, the domain it will be listening on +ara_api_fqdn: "{{ ansible_default_ipv4['address'] }}" + +#################################### +# ara API configuration settings +# For more information, see documentation: https://ara.readthedocs.io +#################################### + +# ARA_BASE_DIR - Default directory for storing data and configuration +ara_base_dir: "{{ ara_root_dir }}/server" + +# ARA_SETTINGS - Path to an ARA API configuration file +ara_settings: "{{ ara_base_dir }}/settings.yaml" + +# ARA_ENV - Environment to load configuration for +ara_env: default + +# ARA_LOG_LEVEL - Log level of the different components +ara_log_level: INFO + +# ARA_LOGGING - Python logging configuration +ara_logging: + disable_existing_loggers: false + formatters: + normal: + format: '%(asctime)s %(levelname)s %(name)s: %(message)s' + handlers: + console: + class: logging.handlers.TimedRotatingFileHandler + formatter: normal + level: "{{ ara_log_level }}" + filename: "{{ ara_log_dir }}/server.log" + when: 'midnight' + interval: 1 + backupCount: 30 + loggers: + ara: + handlers: + - console + level: "{{ ara_log_level }}" + propagate: 0 + root: + handlers: + - console + level: "{{ ara_log_level }}" + version: 1 + +# ARA_CORS_ORIGIN_ALLOW_ALL - django-cors-headers’s CORS_ORIGIN_WHITELIST_ALLOW_ALL setting +ara_cors_origin_allow_all: false + +# ARA_CORS_ORIGIN_WHITELIST - django-cors-headers’s CORS_ORIGIN_WHITELIST setting +ara_cors_origin_whitelist: + - "127.0.0.1:8000" + - "localhost:3000" + +# ARA_SERVER_ALLOWED_HOSTS - Django’s ALLOWED_HOSTS setting +ara_allowed_hosts: + - "127.0.0.1" + - "localhost" + - "::1" + - "{{ ansible_default_ipv4['address'] }}" + +# ARA_DEBUG - Django's DEBUG setting +# If a web server is specified, this will default to false. +# If no web server is specified, this will default to true in order to let +# Django serve a minimum amount of static files. +# It is not recommended to run with debug enabled in production. +ara_debug: "{{ (ara_web_server is none) | ternary(true, false) }}" + +# ARA_SECRET_KEY - Django's SECRET_KEY setting +# Note: If no key is provided, a random one will be generated once and persisted +ara_secret_key: null + +# ARA_STATIC_ROOT - Django’s STATIC_ROOT setting +ara_static_root: "{{ ara_www_dir }}/static" + +# ARA_STATIC_URL - Django's STATIC_URL setting +ara_static_url: /static/ + +# ARA_MEDIA_ROOT - Django's MEDIA_ROOT setting +ara_media_root: "{{ ara_www_dir }}/media" + +# ARA_MEDIA_URL - Django's MEDIA_URL setting +ara_media_url: /media/ + +# ARA_DATABASE_ENGINE - Django’s ENGINE database setting +ara_database_engine: django.db.backends.sqlite3 + +# ARA_DATABASE_NAME - Django’s NAME database setting +ara_database_name: "{{ ara_base_dir }}/ansible.sqlite" + +# ARA_DATABASE_USER - Django’s USER database setting +ara_database_user: null + +# ARA_DATABASE_PASSWORD - Django’s PASSWORD database setting +ara_database_password: null + +# ARA_DATABASE_HOST - Django’s HOST database setting +ara_database_host: null + +# ARA_DATABASE_PORT - Django’s PORT database setting +ara_database_port: null diff --git a/roles/ara/handlers/main.yaml b/roles/ara/handlers/main.yaml new file mode 100644 index 00000000..3e48defd --- /dev/null +++ b/roles/ara/handlers/main.yaml @@ -0,0 +1,37 @@ +--- +# Copyright (c) 2019 Red Hat, Inc. +# +# This file is part of ARA Records Ansible. +# +# ARA Records Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ARA Records Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ARA Records Ansible. If not, see . + +- name: restart nginx + become: yes + service: + name: nginx + state: restarted + when: ara_nginx_enabled is not changed + +- name: restart ara-api + become: yes + service: + name: ara-api + state: restarted + when: ara_api_service_enabled is not changed + +# Is there a better way ? Static files are not created with the httpd context +- name: restore selinux context for static files + become: "{{ (ansible_user_dir in ara_www_dir) | ternary(false, true) }}" + command: "restorecon -Rv {{ ara_www_dir }}" + when: ansible_os_family == "RedHat" diff --git a/roles/ara/meta/main.yaml b/roles/ara/meta/main.yaml new file mode 100644 index 00000000..ea2ed3f8 --- /dev/null +++ b/roles/ara/meta/main.yaml @@ -0,0 +1,35 @@ +--- +# Copyright (c) 2019 Red Hat, Inc. +# +# This file is part of ARA Records Ansible. +# +# ARA Records Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ARA Records Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ARA Records Ansible. If not, see . + +galaxy_info: + author: David Moreau-Simard + description: Self-contained role to set up ARA and it's components + license: GPLv3 + min_ansible_version: 2.7 + platforms: + - name: Fedora + versions: + - 29 + - name: Ubuntu + versions: + - bionic + galaxy_tags: + - ansible + - ara + +dependencies: [] diff --git a/roles/ara/tasks/config.yaml b/roles/ara/tasks/config.yaml new file mode 100644 index 00000000..459e243a --- /dev/null +++ b/roles/ara/tasks/config.yaml @@ -0,0 +1,100 @@ +--- +# Copyright (c) 2019 Red Hat, Inc. +# +# This file is part of ARA Records Ansible. +# +# ARA Records Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ARA Records Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ARA Records Ansible. If not, see . + +- name: Verify if a configuration file exists + stat: + path: "{{ ara_settings }}" + register: settings_stat + +# If no secret key has been provided and this isn't the first time we are +# running, recover the secret key from the existing configuration file. +- when: + - ara_secret_key is none + - settings_stat.stat.exists + block: + - name: Read the configuration file + command: cat "{{ ara_settings }}" + changed_when: false + no_log: yes + register: settings_contents + + - name: Recover existing secret key + vars: + config: "{{ settings_contents.stdout | from_yaml }}" + set_fact: + ara_secret_key: "{{ config[ara_env]['SECRET_KEY'] }}" + no_log: yes + +# If no secret key has been provided and this is the first time we are +# running, generate a new random secret key that will be persisted in the +# configuration file. +- when: + - ara_secret_key is none + - not settings_stat.stat.exists + block: + - name: Generate a random secret key + environment: + PATH: "{{ path_with_virtualenv | default(omit) }}" + command: python3 -c "from django.utils.crypto import get_random_string; print(get_random_string(length=50))" + no_log: yes + register: generated_key + + - name: Set ara_secret_key + set_fact: + ara_secret_key: "{{ generated_key.stdout }}" + no_log: yes + +# Put configuration in a format we can write to a file +- name: Reconcile configuration + vars: + reconciled_configuration: + ALLOWED_HOSTS: "{{ ara_allowed_hosts }}" + BASE_DIR: "{{ ara_base_dir }}" + CORS_ORIGIN_ALLOW_ALL: "{{ ara_cors_origin_allow_all }}" + CORS_ORIGIN_WHITELIST: "{{ ara_cors_origin_whitelist }}" + DATABASES: + default: + ENGINE: "{{ ara_database_engine }}" + NAME: "{{ ara_database_name }}" + USER: "{{ ara_database_user }}" + PASSWORD: "{{ ara_database_password }}" + HOST: "{{ ara_database_host }}" + PORT: "{{ ara_database_port }}" + DEBUG: "{{ ara_debug }}" + LOGGING: "{{ ara_logging }}" + LOG_LEVEL: "{{ ara_log_level }}" + MEDIA_ROOT: "{{ ara_media_root }}" + MEDIA_URL: "{{ ara_media_url }}" + STATIC_ROOT: "{{ ara_static_root }}" + STATIC_URL: "{{ ara_static_url }}" + SECRET_KEY: "{{ ara_secret_key }}" + set_fact: + ara_api_configuration: "{'{{ ara_env }}': {{ reconciled_configuration }} }" + no_log: yes + +- name: Set up the ARA API configuration file + copy: + content: | + --- + # Managed by the ara Ansible role + {{ ara_api_configuration | to_nice_yaml(indent=2) }} + dest: "{{ ara_settings }}" + mode: 0750 + notify: + - restart ara-api + no_log: yes diff --git a/roles/ara/tasks/database_engine/django.db.backends.sqlite3.yaml b/roles/ara/tasks/database_engine/django.db.backends.sqlite3.yaml new file mode 100644 index 00000000..65e8a907 --- /dev/null +++ b/roles/ara/tasks/database_engine/django.db.backends.sqlite3.yaml @@ -0,0 +1,30 @@ +--- +# Copyright (c) 2019 Red Hat, Inc. +# +# This file is part of ARA Records Ansible. +# +# ARA Records Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ARA Records Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ARA Records Ansible. If not, see . + +- name: Ensure the database directory exists + become: "{{ (ansible_user_dir in ara_database_name) | ternary(false, true) }}" + file: + path: "{{ ara_database_name | dirname }}" + state: directory + +- name: Run SQL migrations + become: "{{ (ansible_user_dir in ara_database_name) | ternary(false, true) }}" + environment: + ARA_SETTINGS: "{{ ara_settings }}" + PATH: "{{ path_with_virtualenv | default(omit) }}" + command: ara-manage migrate diff --git a/roles/ara/tasks/install/source.yaml b/roles/ara/tasks/install/source.yaml new file mode 100644 index 00000000..71a7f54e --- /dev/null +++ b/roles/ara/tasks/install/source.yaml @@ -0,0 +1,35 @@ +--- +# Copyright (c) 2019 Red Hat, Inc. +# +# This file is part of ARA Records Ansible. +# +# ARA Records Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ARA Records Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ARA Records Ansible. If not, see . + +- name: Prepare git repository for ara + git: + repo: "{{ ara_source }}" + dest: "{{ ara_source_checkout }}" + version: "{{ (ara_install_version == 'latest') | ternary('HEAD', ara_install_version) }}" + +- name: Install ara + pip: + name: "{{ ara_source_checkout }}" + state: present + virtualenv: "{{ ara_venv | bool | ternary(ara_venv_path, omit) }}" + virtualenv_python: python3 + +- name: Prefix the virtualenv bin directory to PATH + set_fact: + path_with_virtualenv: "{{ ara_venv_path }}/bin:{{ ansible_env.PATH }}" + when: ara_venv | bool diff --git a/roles/ara/tasks/main.yaml b/roles/ara/tasks/main.yaml new file mode 100644 index 00000000..90d11ee6 --- /dev/null +++ b/roles/ara/tasks/main.yaml @@ -0,0 +1,51 @@ +--- +# Copyright (c) 2019 Red Hat, Inc. +# +# This file is part of ARA Records Ansible. +# +# ARA Records Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ARA Records Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ARA Records Ansible. If not, see . + +- name: Include OS family/distribution specific variables + include_vars: "{{ item }}" + with_first_found: + - "{{ ansible_distribution }}.yaml" + - "{{ ansible_os_family }}.yaml" + +- name: Ensure pre-requirements for running are met + include_tasks: pre-requirements.yaml + +- name: Include installation of ARA + include_tasks: "install/{{ ara_install_method }}.yaml" + +- name: Include configuration of the ARA API + include_tasks: config.yaml + +- name: Include configuration of the database engine + include_tasks: "database_engine/{{ ara_database_engine }}.yaml" + +- name: Collect static files + become: "{{ (ansible_user_dir in ara_www_dir) | ternary(false, true) }}" + environment: + ARA_SETTINGS: "{{ ara_settings }}" + PATH: "{{ path_with_virtualenv | default(omit) }}" + command: ara-manage collectstatic --clear --no-input + notify: + - restore selinux context for static files + +- name: Include installation of the WSGI backend server + include_tasks: "wsgi_server/{{ ara_wsgi_server }}.yaml" + +- name: Include installation of the web server + include_tasks: "web_server/{{ ara_web_server }}.yaml" + when: ara_web_server is not none diff --git a/roles/ara/tasks/pre-requirements.yaml b/roles/ara/tasks/pre-requirements.yaml new file mode 100644 index 00000000..06d9d8fe --- /dev/null +++ b/roles/ara/tasks/pre-requirements.yaml @@ -0,0 +1,82 @@ +--- +# Copyright (c) 2019 Red Hat, Inc. +# +# This file is part of ARA Records Ansible. +# +# ARA Records Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ARA Records Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ARA Records Ansible. If not, see . + +# The ansible_python_version fact might end up retrieving the version of +# python2 so we need to explicitely get the version of python 3 available. +- name: Validate availability of Python 3.6 + command: /usr/bin/python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))' + changed_when: false + failed_when: false + register: python_version + +- name: Fail pre-emptively if running Python <3.6 + fail: + msg: "Python >=3.6 is required to run ARA" + when: python_version.stdout is version('3.6', '<') or python_version.rc != 0 + +- name: Get list of installed packages + package_facts: + manager: "auto" + no_log: yes + +- name: Retrieve list of missing required packages + set_fact: + ara_missing_packages: "{{ ara_required_packages | difference(ansible_facts.packages.keys()) }}" + +# Only attempt to elevate privileges if there are any missing packages +- when: ara_missing_packages | length > 0 + block: + - name: Install required packages + become: yes + package: + name: "{{ ara_required_packages }}" + state: present + rescue: + - name: Fail due to missing packages + fail: + msg: "Failed to elevate privileges and install missing required packages. Install the following packages before running this role again: {{ ara_missing_packages | join(' ') }}" + +# The following tasks dynamically enable escalated privileges only when the +# directory to create is not located in the user's home directory. +- name: Ensure ara_root_dir exists + become: "{{ (ansible_user_dir in ara_root_dir) | ternary(false, true) }}" + file: + path: "{{ ara_root_dir }}" + state: directory + mode: 0755 + +- name: Ensure ara_base_dir exists + become: "{{ (ansible_user_dir in ara_base_dir) | ternary(false, true) }}" + file: + path: "{{ ara_base_dir }}" + state: directory + mode: 0750 + +- name: Ensure ara_log_dir exists + become: "{{ (ansible_user_dir in ara_log_dir) | ternary(false, true) }}" + file: + path: "{{ ara_log_dir }}" + state: directory + mode: 0750 + +- name: Ensure ara_www_dir exists + become: "{{ (ansible_user_dir in ara_www_dir) | ternary(false, true) }}" + file: + path: "{{ ara_www_dir }}" + state: directory + mode: 0755 diff --git a/roles/ara/tasks/web_server/nginx.yaml b/roles/ara/tasks/web_server/nginx.yaml new file mode 100644 index 00000000..f3159698 --- /dev/null +++ b/roles/ara/tasks/web_server/nginx.yaml @@ -0,0 +1,54 @@ +--- +# Copyright (c) 2019 Red Hat, Inc. +# +# This file is part of ARA Records Ansible. +# +# ARA Records Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ARA Records Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ARA Records Ansible. If not, see . + +- become: yes + block: + - name: Install nginx + package: + name: nginx + state: present + + - name: Set selinux boolean to allow nginx to reverse proxy + seboolean: + name: httpd_can_network_connect + state: yes + persistent: yes + when: ansible_os_family == "RedHat" + + - name: Set up the ARA API nginx vhost + template: + src: "{{ ara_nginx_api_vhost | default('nginx-ara-api.conf.j2') }}" + dest: "{{ ara_nginx_config_path }}/ara-api.conf" + notify: + - restart nginx + + - name: Enable the nginx configuration on Debian-like systems + file: + src: "{{ ara_nginx_config_path }}/ara-api.conf" + dest: /etc/nginx/sites-enabled/ara-api.conf + state: link + when: ansible_os_family == 'Debian' + notify: + - restart nginx + + - name: Enable and start nginx + service: + name: nginx + state: started + enabled: yes + register: ara_nginx_enabled diff --git a/roles/ara/tasks/wsgi_server/gunicorn.yaml b/roles/ara/tasks/wsgi_server/gunicorn.yaml new file mode 100644 index 00000000..d3866dbc --- /dev/null +++ b/roles/ara/tasks/wsgi_server/gunicorn.yaml @@ -0,0 +1,44 @@ +--- +# Copyright (c) 2019 Red Hat, Inc. +# +# This file is part of ARA Records Ansible. +# +# ARA Records Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ARA Records Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ARA Records Ansible. If not, see . + +- name: Install gunicorn + pip: + name: gunicorn + state: present + virtualenv: "{{ ara_venv | bool | ternary(ara_venv_path, omit) }}" + virtualenv_python: python3 + +- become: yes + block: + - name: Set up systemd unit file for gunicorn to run the ARA API + template: + src: ara-api.service.j2 + dest: /etc/systemd/system/ara-api.service + owner: root + group: root + mode: 0644 + notify: + - restart ara-api + + - name: Enable and start ara-api with gunicorn + service: + name: ara-api + state: started + enabled: yes + daemon_reload: yes + register: ara_api_service_enabled diff --git a/roles/ara/templates/ara-api.service.j2 b/roles/ara/templates/ara-api.service.j2 new file mode 100644 index 00000000..6f943532 --- /dev/null +++ b/roles/ara/templates/ara-api.service.j2 @@ -0,0 +1,21 @@ +[Unit] +Description=ARA Records Ansible API with gunicorn +After=network.target + +[Service] +PIDFile=/run/ara-api/pid +User={{ ansible_user }} +RuntimeDirectory=ara-api +WorkingDirectory={{ ara_root_dir }} +Environment=ARA_SETTINGS={{ ara_settings }} +{% if ara_venv %} +ExecStart={{ ara_venv_path }}/bin/gunicorn --pid /run/ara-api/pid --workers=4 --bind {{ ara_wsgi_bind }} ara.server.wsgi +{% else %} +ExecStart=gunicorn --pid /run/ara-api/pid --workers=4 --bind {{ ara_wsgi_bind }} ara.server.wsgi +{% endif %} +ExecReload=/bin/kill -s HUP $MAINPID +ExecStop=/bin/kill -s TERM $MAINPID +PrivateTmp=true + +[Install] +WantedBy=multi-user.target diff --git a/roles/ara/templates/nginx-ara-api.conf.j2 b/roles/ara/templates/nginx-ara-api.conf.j2 new file mode 100644 index 00000000..be84c4f2 --- /dev/null +++ b/roles/ara/templates/nginx-ara-api.conf.j2 @@ -0,0 +1,40 @@ +upstream ara_api { + # fail_timeout=0 means we always retry an upstream even if it failed + # to return a good HTTP response + server {{ ara_wsgi_bind }} fail_timeout=0; +} + +server { + listen 80; + keepalive_timeout 5; + server_name {{ ara_api_fqdn }}; + + access_log /var/log/nginx/{{ ara_api_fqdn }}_access.log; + error_log /var/log/nginx/{{ ara_api_fqdn }}_error.log; + + # /static contains files from "ara-manage collectstatic" + location /static { + alias {{ ara_www_dir }}/static; + expires 7d; + add_header Cache-Control "public"; + } + + # There's nothing at /, redirect it to the actual API for convenience + location / { + return 301 http://{{ ara_api_fqdn }}/api/v1/; + } + + location /api/v1/ { + # checks if the file exists, if not found proxy to app + try_files $uri @proxy_to_app; + } + + location @proxy_to_app { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + + proxy_redirect off; + proxy_pass http://ara_api; + } +} diff --git a/roles/ara/vars/Fedora.yaml b/roles/ara/vars/Fedora.yaml new file mode 100644 index 00000000..09addbf9 --- /dev/null +++ b/roles/ara/vars/Fedora.yaml @@ -0,0 +1,27 @@ +--- +# Copyright (c) 2019 Red Hat, Inc. +# +# This file is part of ARA Records Ansible. +# +# ARA Records Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ARA Records Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ARA Records Ansible. If not, see . + +ara_required_packages: + - git + - python3-virtualenv + - python3-libselinux + - policycoreutils-python-utils + +ara_nginx_user: nginx +ara_nginx_group: nginx +ara_nginx_config_path: /etc/nginx/conf.d diff --git a/roles/ara/vars/Ubuntu.yaml b/roles/ara/vars/Ubuntu.yaml new file mode 100644 index 00000000..068037d4 --- /dev/null +++ b/roles/ara/vars/Ubuntu.yaml @@ -0,0 +1,25 @@ +--- +# Copyright (c) 2019 Red Hat, Inc. +# +# This file is part of ARA Records Ansible. +# +# ARA Records Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ARA Records Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ARA Records Ansible. If not, see . + +ara_required_packages: + - git + - python3-venv + +ara_nginx_user: www-data +ara_nginx_group: www-data +ara_nginx_config_path: /etc/nginx/sites-available diff --git a/tests/integration-post.yaml b/tests/integration-post.yaml index ad097a89..528daca8 100644 --- a/tests/integration-post.yaml +++ b/tests/integration-post.yaml @@ -1,20 +1,20 @@ --- -# Copyright (c) 2018 Red Hat, Inc. +# Copyright (c) 2019 Red Hat, Inc. # # This file is part of ARA Records Ansible. # -# ARA is free software: you can redistribute it and/or modify +# ARA Records Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # -# ARA is distributed in the hope that it will be useful, +# ARA Records Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with ARA. If not, see . +# along with ARA Records Ansible. If not, see . - name: Integration tests post-run hosts: all diff --git a/tests/role-integration-pre.yaml b/tests/role-integration-pre.yaml new file mode 100644 index 00000000..e39daec4 --- /dev/null +++ b/tests/role-integration-pre.yaml @@ -0,0 +1,35 @@ +--- +# Copyright (c) 2018 Red Hat, Inc. +# +# This file is part of ARA Records Ansible. +# +# ARA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ARA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ARA. If not, see . + +- name: Role integration pre-run + hosts: all + gather_facts: yes + tasks: + # The images built with diskimage-builder by the OpenStack infrastructure + # currently installs the virtualenv packages on Fedora and adds them to the + # exclude list in /etc/dnf/dnf.conf. + # Work around this since it prevents the role from working properly. + # When it attempts to install python3-virtualenv, it won't be found because of the exclude. + # Reference: https://github.com/openstack/diskimage-builder/blob/5b1844acf99d3797b1bbe02601e5ce94308cab55/diskimage_builder/elements/pip-and-virtualenv/install.d/pip-and-virtualenv-source-install/04-install-pip#L134 + - name: Remove DNF excludes for Fedora + become: yes + lineinfile: + path: /etc/dnf/dnf.conf + regexp: "^exclude=" + state: absent + when: ansible_distribution == "Fedora"