From 064b87b54f68c4498485d48606067d07f8ca2bd5 Mon Sep 17 00:00:00 2001 From: David Moreau Simard Date: Thu, 7 Mar 2019 18:11:41 -0500 Subject: [PATCH] Import role for deploying ara-web The ara_web role is intended as a way to deploy and install the ara-web project in different ways. The commit also adds integration test jobs for the role. The integration job names and general layout will be cleaned up in a future patch. Change-Id: Ib59c455bb38f107fef3d5aca3dff42b6f7eac8a7 --- .zuul.yaml | 35 ++++++ doc/source/_static/ansible-role-ara-web.png | Bin 0 -> 34924 bytes doc/source/ansible-role-ara-web.rst | 4 + doc/source/index.rst | 1 + doc/source/installation.rst | 9 +- playbooks/ara_web.yaml | 5 + roles/ara_frontend_nginx/README.rst | 19 ++- roles/ara_frontend_nginx/tasks/main.yaml | 51 +++++--- .../templates/ara-web.conf.j2 | 33 ++++++ roles/ara_web/README.rst | 111 ++++++++++++++++++ roles/ara_web/defaults/main.yaml | 71 +++++++++++ roles/ara_web/handlers/main.yaml | 31 +++++ roles/ara_web/meta/main.yaml | 36 ++++++ roles/ara_web/tasks/install/source.yaml | 107 +++++++++++++++++ roles/ara_web/tasks/main.yaml | 28 +++++ roles/ara_web/tasks/nodejs.yaml | 53 +++++++++ roles/ara_web/templates/ara-web.service.j2 | 16 +++ tests/role-ara-web-post.yaml | 46 ++++++++ 18 files changed, 630 insertions(+), 26 deletions(-) create mode 100644 doc/source/_static/ansible-role-ara-web.png create mode 100644 doc/source/ansible-role-ara-web.rst create mode 100644 playbooks/ara_web.yaml create mode 100644 roles/ara_frontend_nginx/templates/ara-web.conf.j2 create mode 100644 roles/ara_web/README.rst create mode 100644 roles/ara_web/defaults/main.yaml create mode 100644 roles/ara_web/handlers/main.yaml create mode 100644 roles/ara_web/meta/main.yaml create mode 100644 roles/ara_web/tasks/install/source.yaml create mode 100644 roles/ara_web/tasks/main.yaml create mode 100644 roles/ara_web/tasks/nodejs.yaml create mode 100644 roles/ara_web/templates/ara-web.service.j2 create mode 100644 tests/role-ara-web-post.yaml diff --git a/.zuul.yaml b/.zuul.yaml index e641fcf3..477f0f72 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -76,6 +76,37 @@ parent: ara-1.0-role-integration-base nodeset: fedora-latest +- job: + name: ara-web-role-integration-base + parent: base + vars: + ara_web_source: "{{ ansible_user_dir }}/src/git.openstack.org/openstack/ara-web" + ara_web_api_server: "https://api.demo.recordsansible.org" + files: + - playbooks/* + - roles/ara_frontend_nginx/* + - roles/ara_web/* + - src/.* + - public/.* + - package.json + - package-lock.json + - .zuul.d/* + required-projects: + - openstack/ara-web + - openstack/ara-infra + run: playbooks/ara_web.yaml + post-run: tests/role-ara-web-post.yaml + +- job: + name: ara-web-role-integration-ubuntu + parent: ara-web-role-integration-base + nodeset: ubuntu-bionic + +- job: + name: ara-web-role-integration-fedora + parent: ara-web-role-integration-base + nodeset: fedora-latest + - project: vars: rtd_webhook_id: '49230' @@ -89,6 +120,8 @@ - ara-1.0-integration-ubuntu-2.6 - ara-1.0-role-integration-ubuntu - ara-1.0-role-integration-fedora + - ara-web-role-integration-ubuntu + - ara-web-role-integration-fedora - ara-tox-linters - ara-tox-py3 gate: @@ -97,5 +130,7 @@ - ara-1.0-integration-ubuntu-2.6 - ara-1.0-role-integration-ubuntu - ara-1.0-role-integration-fedora + - ara-web-role-integration-ubuntu + - ara-web-role-integration-fedora - ara-tox-linters - ara-tox-py3 diff --git a/doc/source/_static/ansible-role-ara-web.png b/doc/source/_static/ansible-role-ara-web.png new file mode 100644 index 0000000000000000000000000000000000000000..07a6bae98132b4f0becbf126f48733eae267360c GIT binary patch literal 34924 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 Vw^{c!g9^W-B^6F3p16GPe*tQXyy^e| literal 0 HcmV?d00001 diff --git a/doc/source/ansible-role-ara-web.rst b/doc/source/ansible-role-ara-web.rst new file mode 100644 index 00000000..a86bd4bd --- /dev/null +++ b/doc/source/ansible-role-ara-web.rst @@ -0,0 +1,4 @@ +.. _ansible-role-ara-web: + +.. include:: ../../roles/ara_web/README.rst + :end-before: include_delimiter_end diff --git a/doc/source/index.rst b/doc/source/index.rst index 4bd417c3..6f23b31b 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -18,3 +18,4 @@ Table of Contents :maxdepth: 1 ansible-role-ara-api + ansible-role-ara-web diff --git a/doc/source/installation.rst b/doc/source/installation.rst index 234ee1b5..537ad836 100644 --- a/doc/source/installation.rst +++ b/doc/source/installation.rst @@ -17,9 +17,10 @@ in order to avoid conflicts with your Linux distribution python packages:: # Install ARA 1.0 from source ~/.ara/venv/bin/pip install git+https://git.openstack.org/openstack/ara@feature/1.0 -Using ansible-role-ara-api ---------------------------- +Using Ansible roles +------------------- -An Ansible role is available to install and configure ARA. +Two roles are built-in to help users configure their API and web deployments: -For more details, refer to the role documentation: :ref:`ansible-role-ara-api`. +- :ref:`ansible-role-ara-api` to install the python parts (Ansible plugins, API server) +- :ref:`ansible-role-ara-web` to install the web client (nodejs, react+patternfly) diff --git a/playbooks/ara_web.yaml b/playbooks/ara_web.yaml new file mode 100644 index 00000000..e2d24a42 --- /dev/null +++ b/playbooks/ara_web.yaml @@ -0,0 +1,5 @@ +- name: Install ara-web with default settings + hosts: all + gather_facts: yes + roles: + - ara_web diff --git a/roles/ara_frontend_nginx/README.rst b/roles/ara_frontend_nginx/README.rst index b77d6311..3e0b30a3 100644 --- a/roles/ara_frontend_nginx/README.rst +++ b/roles/ara_frontend_nginx/README.rst @@ -13,27 +13,36 @@ Role Variables -------------- - ``ara_api_frontend_vhost``: Path to a custom nginx vhost configuration file for ara-api. +- ``ara_web_frontend_vhost``: Path to a custom nginx vhost configuration file for ara-web. -Example playbook ----------------- +Example playbooks +----------------- Install ARA and set up the API to be served by nginx with a custom vhost configuration 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 + # The web interface will be reachable at http://web.ara.example.org + # The web interface will be set up to query api.ara.example.org. + - name: Deploy ARA API server and web interface hosts: all gather_facts: yes vars: + # ara_api ara_api_frontend_server: nginx ara_api_wsgi_server: gunicorn ara_api_fqdn: api.ara.example.org ara_api_allowed_hosts: - api.ara.example.org - ara_api_frontend_vhost: custom_vhost.conf.j2 + ara_api_frontend_vhost: custom_api_vhost.conf.j2 + # ara_web + ara_web_fqdn: web.ara.example.org + ara_web_api_endpoint: "http://api.ara.example.org" + ara_web_frontend_server: nginx + ara_web_frontend_vhost: custom_web_vhost.conf.j2 roles: - ara_api + - ara_web Copyright --------- diff --git a/roles/ara_frontend_nginx/tasks/main.yaml b/roles/ara_frontend_nginx/tasks/main.yaml index 89567159..672bb740 100644 --- a/roles/ara_frontend_nginx/tasks/main.yaml +++ b/roles/ara_frontend_nginx/tasks/main.yaml @@ -36,24 +36,41 @@ persistent: yes when: ansible_os_family == "RedHat" - - name: Set up the ARA API nginx vhost - template: - src: "{{ ara_api_frontend_vhost | default('ara-api.conf.j2') }}" - dest: "{{ ara_nginx_config_path }}/ara-api.conf" - notify: - - restart nginx - when: - - ara_api_fqdn is defined - - ara_api_wsgi_bind is defined + - when: ara_api_fqdn is defined + block: + - name: Set up the ARA API nginx vhost + template: + src: "{{ ara_api_frontend_vhost | default('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 the API 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 + + - when: ara_web_fqdn is defined + block: + - name: Set up the ARA API nginx vhost + template: + src: "{{ ara_web_frontend_vhost | default('ara-web.conf.j2') }}" + dest: "{{ ara_nginx_config_path }}/ara-web.conf" + notify: + - restart nginx + + - name: Enable the web nginx configuration on Debian-like systems + file: + src: "{{ ara_nginx_config_path }}/ara-web.conf" + dest: /etc/nginx/sites-enabled/ara-web.conf + state: link + when: ansible_os_family == 'Debian' + notify: + - restart nginx - name: Enable and start nginx service: diff --git a/roles/ara_frontend_nginx/templates/ara-web.conf.j2 b/roles/ara_frontend_nginx/templates/ara-web.conf.j2 new file mode 100644 index 00000000..1fd01a60 --- /dev/null +++ b/roles/ara_frontend_nginx/templates/ara-web.conf.j2 @@ -0,0 +1,33 @@ +{% if ara_web_dev_server %} +upstream ara_web { + # fail_timeout=0 means we always retry an upstream even if it failed + # to return a good HTTP response + server {{ ara_web_dev_server_bind_address }}:{{ ara_web_dev_server_bind_port }} fail_timeout=0; +} +{% endif %} + +server { + listen 80; + keepalive_timeout 5; + server_name {{ ara_web_fqdn }}; + root {{ ara_web_static_dir }}; + + access_log /var/log/nginx/{{ ara_web_fqdn }}_access.log; + error_log /var/log/nginx/{{ ara_web_fqdn }}_error.log; + + {% if ara_web_dev_server %} + location / { + # 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_web; + } + {% endif %} +} diff --git a/roles/ara_web/README.rst b/roles/ara_web/README.rst new file mode 100644 index 00000000..72cb976f --- /dev/null +++ b/roles/ara_web/README.rst @@ -0,0 +1,111 @@ +ansible-role-ara-web +==================== + +.. image:: ../../doc/source/_static/ansible-role-ara-web.png + +This Ansible role provides a framework for installing one or many instances of +`ara-web `_ 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 `_. + +.. literalinclude:: ../../roles/ara_web/defaults/main.yaml + :language: yaml+jinja + :start-after: www.gnu.org + +TL;DR +------ + +This is what the role does by default out of the box: + +- Retrieves ara-web from source +- Installs nodejs LTS (v10) +- Installs ara-web dependencies with npm +- Configures an ara-server API endpoint in ara-web's ``public/config.json`` file +- Sets up a systemd unit file for running ara-web with the embedded development server + +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 defaults used to provide the +topology from the ``TL;DR`` above: + +- ``ara_web_install_method: source`` +- ``ara_web_dev_server: true`` +- ``ara_web_frontend_server: null`` + +The intent is that as the role gains support for other install methods or +frontend servers, it will be possible to mix and match according to preference +or requirements. + +Example playbooks +----------------- + +Deploy the ARA API and web client on the same machine with defaults: + +.. code-block:: yaml+jinja + + - name: Deploy ARA API and web client + hosts: all + gather_facts: yes + vars: + # ara_api + ara_api_fqdn: api.ara.example.org + ara_api_wsgi_server: gunicorn + ara_api_allowed_hosts: + - api.ara.example.org + ara_api_cors_origin_whitelist: + - web.ara.example.org + # ara_web + ara_web_fqdn: web.ara.example.org + ara_web_api_endpoint: "http://api.ara.example.org" + roles: + - ara_api + - ara_web + +Deploy only ara-web behind nginx and point it to a remote API endpoint: + +.. code-block:: yaml+jinja + + # Note: Don't forget to add the web fqdn in the remote cors_origin_whitelist. + # Otherwise, the web client might not be authorized to query the API. + - name: Deploy ara-web for remote API endpoint + hosts: all + gather_facts: yes + vars: + ara_web_fqdn: web.ara.example.org + ara_web_api_endpoint: "http://api.remoteara.example.org" + ara_web_frontend_server: nginx + ara_web_frontend_vhost: custom-web-vhost.conf.j2 + roles: + - ara_web + +.. _include_delimiter_end: + +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_web/defaults/main.yaml b/roles/ara_web/defaults/main.yaml new file mode 100644 index 00000000..b7319414 --- /dev/null +++ b/roles/ara_web/defaults/main.yaml @@ -0,0 +1,71 @@ +--- +# 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 of where files will be stored for ara-web +ara_web_root_dir: "{{ ansible_user_dir }}/.ara" + +# When using static builds without the dev server, path to ara-web static assets +ara_web_static_dir: "{{ ara_web_root_dir }}/www/ara-web" + +# How ara-web will be installed +# - source (default): installs from a local or remote git repository specified by ara_web_source +# - npm (planned): installs from npm +ara_web_install_method: source + +# When installing from source, the location of the remote or local git repository +ara_web_source: "https://git.openstack.org/openstack/ara-web" + +# Location where ara-web will be checked out +ara_web_source_checkout: "{{ ara_web_root_dir }}/git/ara-web" + +# Location where node_modules will be installed +ara_web_node_modules_dir: "{{ ara_web_source_checkout }}" + +# Version of ara-web to install +# This can be a git ref (tag, branch, commit) when installed from source +# When using "latest" as the source version, HEAD will be used +ara_web_version: latest + +# Whether to use the embedded react web server or not +# Setting this to false means ara-web will be statically built instead +ara_web_dev_server: true + +# When the development server is enabled, the address it will be listening on +ara_web_dev_server_bind_address: 127.0.0.1 + +# When the development server is enabled, the port it will be listening on +ara_web_dev_server_bind_port: 3000 + +# Version of nodesource nodejs repositories to install +ara_web_nodejs_version: 10 + +# ara-server API endpoint to use +ara_web_api_endpoint: "http://127.0.0.1:8000" + +# The frontend server for serving ara-web +# - null (default): none, users are expected to use the development server directly or deploy their own web server +# - nginx: when performance of the development server is an issue +# - apache (planned) +ara_web_frontend_server: null + +# When using a frontend server, you can override the default vhost configuration +# template by specifying the path to your own template file. +ara_web_frontend_vhost: null + +# When using a frontend server, the hostname to listen on +ara_web_fqdn: "{{ ansible_default_ipv4['address'] }}" diff --git a/roles/ara_web/handlers/main.yaml b/roles/ara_web/handlers/main.yaml new file mode 100644 index 00000000..bb7feae4 --- /dev/null +++ b/roles/ara_web/handlers/main.yaml @@ -0,0 +1,31 @@ +--- +# 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 . + +# 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_web_static_dir) | ternary(false, true) }}" + command: "restorecon -Rv {{ ara_web_static_dir }}" + when: ansible_os_family == "RedHat" + +- name: restart ara-web + become: true + service: + name: ara-web + state: restarted + when: + - ara_web_service_enabled is not changed diff --git a/roles/ara_web/meta/main.yaml b/roles/ara_web/meta/main.yaml new file mode 100644 index 00000000..e94836dc --- /dev/null +++ b/roles/ara_web/meta/main.yaml @@ -0,0 +1,36 @@ +--- +# 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: Role to set up ara-web + license: GPLv3 + min_ansible_version: 2.7 + platforms: + - name: Fedora + versions: + - 29 + - name: Ubuntu + versions: + - bionic + galaxy_tags: + - ansible + - ara + - ara-web + +dependencies: [] diff --git a/roles/ara_web/tasks/install/source.yaml b/roles/ara_web/tasks/install/source.yaml new file mode 100644 index 00000000..7dc07590 --- /dev/null +++ b/roles/ara_web/tasks/install/source.yaml @@ -0,0 +1,107 @@ +--- +# 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 libselinux-python is installed for Red Hat derivatives + become: yes + package: + name: libselinux-python + state: present + when: ansible_os_family == "RedHat" + +- name: Ensure git is installed + become: yes + package: + name: git + state: present + +# TODO: node_modules and public/config.json are local to the git repository so this is not idempotent +- name: Prepare git repository for ara-web + git: + repo: "{{ ara_web_source }}" + dest: "{{ ara_web_source_checkout }}" + version: "{{ (ara_web_version == 'latest') | ternary('HEAD', ara_web_version) }}" + force: yes + +- name: Install ara-web npm dependencies + npm: + path: "{{ ara_web_source_checkout }}" + global: no + production: yes + state: present + notify: + - restart ara-web + +- name: Configure ara-server API endpoint for ara-web + vars: + web_config: + apiURL: "{{ ara_web_api_endpoint }}" + copy: + content: "{{ web_config | to_nice_json(indent=2) }}" + dest: "{{ ara_web_source_checkout }}/public/config.json" + mode: 0644 + notify: + - restart ara-web + +- when: ara_web_dev_server | bool + become: yes + block: + - name: Set up systemd unit file for ara-web + template: + src: ara-web.service.j2 + dest: /etc/systemd/system/ara-web.service + owner: root + group: root + mode: 0644 + notify: + - restart ara-web + + - name: Enable and start ara-web + service: + name: ara-web + state: started + enabled: yes + daemon_reload: yes + register: ara_web_service_enabled + +- when: not ara_web_dev_server | bool + block: + - name: Stop and disable ara-web + become: yes + service: + name: ara-web + state: stopped + enabled: no + + - name: Ensure systemd unit file is not configured + become: yes + file: + path: /etc/systemd/system/ara-web.service + state: absent + + - name: Run a production build of ara-web + command: npm run build + args: + chdir: "{{ ara_web_source_checkout }}" + creates: "{{ ara_web_source_checkout }}/build" + + - name: Synchronize build to web directory + become: "{{ (ansible_user_dir in ara_web_static_dir) | ternary(false, true) }}" + command: | + rsync -rlog --delete-delay {{ ara_web_source_checkout }}/build/ {{ ara_web_static_dir }} + notify: + - restore selinux context for static files diff --git a/roles/ara_web/tasks/main.yaml b/roles/ara_web/tasks/main.yaml new file mode 100644 index 00000000..c358d4c2 --- /dev/null +++ b/roles/ara_web/tasks/main.yaml @@ -0,0 +1,28 @@ +--- +# 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 nodejs + include_tasks: nodejs.yaml + +- name: Include ara-web installation + include_tasks: install/{{ ara_web_install_method }}.yaml + +- name: Include frontend server installation + include_role: + name: "ara_frontend_{{ ara_web_frontend_server }}" + when: ara_web_frontend_server is not none diff --git a/roles/ara_web/tasks/nodejs.yaml b/roles/ara_web/tasks/nodejs.yaml new file mode 100644 index 00000000..1df91802 --- /dev/null +++ b/roles/ara_web/tasks/nodejs.yaml @@ -0,0 +1,53 @@ +--- +# 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 . + +- when: ansible_os_family == "Debian" + become: yes + block: + - name: Install apt-transport-https + package: + name: apt-transport-https + state: present + + - name: Install nodesource repository key + apt_key: + url: "https://deb.nodesource.com/gpgkey/nodesource.gpg.key" + + - name: Install nodesource apt source repository + apt_repository: + repo: "deb-src https://deb.nodesource.com/node_{{ ara_web_nodejs_version }}.x {{ ansible_distribution_release }} main" + state: present + + - name: Install nodesource apt repository + apt_repository: + repo: "deb https://deb.nodesource.com/node_{{ ara_web_nodejs_version }}.x {{ ansible_distribution_release }} main" + state: present + update_cache: yes + +- name: Install Nodesource yum repository + become: yes + package: + name: "https://rpm.nodesource.com/pub_{{ ara_web_nodejs_version }}.x/fc/{{ ansible_distribution_major_version }}/{{ ansible_architecture }}/nodesource-release-fc{{ ansible_distribution_major_version }}-1.noarch.rpm" + state: present + when: ansible_os_family == "RedHat" + +- name: Install nodejs + become: yes + package: + name: nodejs + state: present diff --git a/roles/ara_web/templates/ara-web.service.j2 b/roles/ara_web/templates/ara-web.service.j2 new file mode 100644 index 00000000..95685e3c --- /dev/null +++ b/roles/ara_web/templates/ara-web.service.j2 @@ -0,0 +1,16 @@ +[Unit] +Description=ARA Records Ansible web client +After=network.target + +[Service] +PIDFile=/run/ara-web/pid +User={{ ansible_user_id }} +RuntimeDirectory=ara-web +WorkingDirectory={{ ara_web_source_checkout }} +ExecStart=/usr/bin/npm start --host {{ ara_web_dev_server_bind_address }} --port {{ ara_web_dev_server_bind_port }} +ExecReload=/bin/kill -s HUP $MAINPID +ExecStop=/bin/kill -s TERM $MAINPID +PrivateTmp=true + +[Install] +WantedBy=multi-user.target diff --git a/tests/role-ara-web-post.yaml b/tests/role-ara-web-post.yaml new file mode 100644 index 00000000..3a74ad12 --- /dev/null +++ b/tests/role-ara-web-post.yaml @@ -0,0 +1,46 @@ +- name: Deploy ara-web + hosts: all + gather_facts: yes + vars: + ara_web_source_checkout: "{{ ansible_user_dir }}/.ara/git/ara-web" + config: + apiURL: "https://api.demo.recordsansible.org" + tasks: + # Before building the application, we need to set the homepage argument + # from package.json to use the URL where logs will be uploaded. + - name: Resolve Zuul log path + include_role: + name: set-zuul-log-path-fact + + - name: Read package.json + command: "cat {{ ara_web_source_checkout }}/package.json" + register: package_json + + - name: Set homepage parameter + vars: + build_url: "http://logs.openstack.org/{{ zuul_log_path }}/build" + set_fact: + package_json: "{{ package_json.stdout | from_json | combine({'homepage': build_url}) }}" + + - name: Write package.json + copy: + content: "{{ package_json | to_nice_json }}" + dest: "{{ ara_web_source_checkout }}/package.json" + + - name: Set config.json to use api.demo.recordsansible.org + copy: + content: "{{ config | to_nice_json }}" + dest: "{{ ara_web_source_checkout }}/public/config.json" + + - name: Run a production build of ara-web + command: npm run build + args: + chdir: "{{ ara_web_source_checkout }}" + creates: "{{ ara_web_source_checkout }}/build" + + - name: Upload build to log server + synchronize: + src: "{{ ara_web_source_checkout }}/build" + dest: "{{ zuul.executor.log_root }}" + mode: pull + verify_host: true