From 2e9c59287bfbc46c2f9d89de165abdd1dcf2641c Mon Sep 17 00:00:00 2001 From: Ludo Date: Mon, 20 Jan 2025 16:19:01 +0100 Subject: [PATCH] feat: doc CI + doc deploy --- .github/workflows/docs.yml | 43 ++++++++++++++ deploy/docs.yml | 4 ++ deploy/group_vars/prod/vars.yml | 1 + deploy/roles/docs/handlers/main.yml | 5 ++ deploy/roles/docs/tasks/main.yml | 58 +++++++++++++++++++ deploy/roles/docs/templates/nginx.conf.j2 | 69 +++++++++++++++++++++++ deploy/roles/docs/vars/main.yml | 18 ++++++ 7 files changed, 198 insertions(+) create mode 100644 .github/workflows/docs.yml create mode 100644 deploy/docs.yml create mode 100644 deploy/roles/docs/handlers/main.yml create mode 100644 deploy/roles/docs/tasks/main.yml create mode 100644 deploy/roles/docs/templates/nginx.conf.j2 create mode 100644 deploy/roles/docs/vars/main.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..be80964 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,43 @@ +name: Build Documentation + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + if: "!contains(github.event.head_commit.message, '[no doc]')" + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install mkdocs + pip install -r requirements.txt # If you have additional dependencies + + - name: Build documentation + run: | + mkdocs build --strict + + name: Deploy doc + uses: dawidd6/action-ansible-playbook@v2 + with: + playbook: docs.yml + directory: deploy + vault_password: ${{secrets.ANSIBLE_VAULT_KEY}} + options: | + -l prod + key: ${{ secrets.SSH_PRIVATE_KEY }} diff --git a/deploy/docs.yml b/deploy/docs.yml new file mode 100644 index 0000000..4da7e42 --- /dev/null +++ b/deploy/docs.yml @@ -0,0 +1,4 @@ +--- +- hosts: all + roles: + - docs diff --git a/deploy/group_vars/prod/vars.yml b/deploy/group_vars/prod/vars.yml index 28df257..030ab4f 100644 --- a/deploy/group_vars/prod/vars.yml +++ b/deploy/group_vars/prod/vars.yml @@ -22,5 +22,6 @@ backend_static_path: "{{ project_dir }}/backend_static" frontend_branch: main backend_branch: main showcase_branch: main +docs_branch: main environment_name: production diff --git a/deploy/roles/docs/handlers/main.yml b/deploy/roles/docs/handlers/main.yml new file mode 100644 index 0000000..4e0a6ca --- /dev/null +++ b/deploy/roles/docs/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: reload nginx + systemd: + name: nginx + state: reloaded diff --git a/deploy/roles/docs/tasks/main.yml b/deploy/roles/docs/tasks/main.yml new file mode 100644 index 0000000..8d10c7d --- /dev/null +++ b/deploy/roles/docs/tasks/main.yml @@ -0,0 +1,58 @@ +- name: Copy nginx config + template: + src: nginx.conf.j2 + dest: /etc/nginx/sites-enabled/{{ project_slug }}-showcase + owner: root + group: root + mode: 0644 + notify: + - reload nginx + +- name: get latest docs code + git: + repo: "{{ frontend_repo }}" + dest: "{{ docs_path }}/.." + key_file: "{{ backend_identity_file_path }}" + accept_hostkey: true + force: true + version: "{{ docs_branch }}" + become_user: "{{ main_user }}" + register: clonecode + +- name: Install MkDocs and dependencies + pip: + name: + - mkdocs + - mkdocs-material + executable: pip3 + become: true + +- name: Build documentation using MkDocs + command: mkdocs build --clean --strict + args: + chdir: "{{ docs_path }}" + become_user: "{{ main_user }}" + when: clonecode.changed or force_update is defined + +- name: update docs static folder content + synchronize: + src: "{{ docs_path }}/site/" + dest: "{{ docs_static_path }}/" + rsync_opts: + - "-a" + - "--delete" + - "--chown=www-data:www-data" + delegate_to: "{{ inventory_hostname }}" + when: clonecode.changed or force_update is defined + +- name: Check if MkDocs site is available + uri: + url: "http://{{ main_hostname }}" + status_code: 200 + register: site_status + ignore_errors: true + +- name: Fail if site is not accessible + fail: + msg: "Documentation site is not accessible!" + when: site_status.status != 200 diff --git a/deploy/roles/docs/templates/nginx.conf.j2 b/deploy/roles/docs/templates/nginx.conf.j2 new file mode 100644 index 0000000..3796753 --- /dev/null +++ b/deploy/roles/docs/templates/nginx.conf.j2 @@ -0,0 +1,69 @@ +{{ ansible_managed | comment }} + +server { + + {% if https_enabled -%} + listen 443 ssl; + + ssl_certificate /etc/letsencrypt/live/{{ certificate_hostname }}/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/{{ certificate_hostname }}/privkey.pem; # managed by Certbot + include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot + {% else -%} + listen 80; + {% endif %} + + server_name {{ " ".join(all_hostnames) }}; + + access_log /var/log/nginx/access_{{ main_hostname.replace('.', '_') }}.log; + error_log /var/log/nginx/error_{{ main_hostname.replace('.', '_') }}.log; + + {% for hostname in redirect_hostnames -%} + if ($host = {{ hostname }}) { + return 301 https://{{ main_hostname }}$request_uri; + } # managed by Certbot + {% endfor %} + + # http://stackoverflow.com/questions/15238506/djangos-suspiciousoperation-invalid-http-host-header + # Deny illegal Host headers + if ($host !~* ^({{ "|".join(all_hostnames) }})$ ) { + return 444; + } + + # gzip compression + gzip on; + gzip_vary on; + gzip_types application/json application/javascript application/xml text/css text/javascript text/plain text/xml image/svg+xml; + + proxy_read_timeout {{ requests_timeout }}; + proxy_connect_timeout {{ requests_timeout }}; + proxy_send_timeout {{ requests_timeout }}; + send_timeout {{ requests_timeout }}; + + # send all + location / { + root {{ showcase_static_path }}; + } + +} + +{% if https_enabled %} +server { + {% for hostname in public_hostnames -%} + if ($host = {{ hostname }}) { + return 301 https://$host$request_uri; + } # managed by Certbot + {% endfor %} + + {% for hostname in redirect_hostnames -%} + if ($host = {{ hostname }}) { + return 301 https://{{ main_hostname }}$request_uri; + } # managed by Certbot + {% endfor %} + + server_name {{ " ".join(all_hostnames) }}; + + listen 80; + return 404; # managed by Certbot +} +{% endif %} diff --git a/deploy/roles/docs/vars/main.yml b/deploy/roles/docs/vars/main.yml new file mode 100644 index 0000000..d5d4280 --- /dev/null +++ b/deploy/roles/docs/vars/main.yml @@ -0,0 +1,18 @@ +--- +showcase_static_path: "{{ project_dir }}/showcase_static" +showcase_path: "{{ project_dir }}/docs/static" + +main_hostname: "docs.iarbre.fr" +public_hostnames: + - "{{ main_hostname }}" +# hostnames mentioned here are redirected to the main hostname +redirect_hostnames: + - www.iarbre.fr +all_hostnames: "{{ public_hostnames + redirect_hostnames }}" +# the certificate that you see in /etc/nginx/sites-enabled/{{ project_slug }}, +# in the line like /etc/letsencrypt/live/[certificate_hostname]/fullchain.pem +certificate_hostname: "iarbre.fr" +# only change this variable after having: +# - run the playbook at least once with the value `false` +# - run `certbot --nginx` manually on the server +https_enabled: true