--- - name: Validate the documentation artifact and approved host ansible.builtin.assert: that: - documentation_revision is match('^[0-9a-f]{40}$') - documentation_sha256 is match('^[0-9a-f]{64}$') - documentation_mode in ['development', 'public'] - documentation_hostname == ('docs.localtest.me' if documentation_mode == 'development' else 'docs.staging.pharosfactory.ai') - ansible_facts.os_family in ['Debian', 'RedHat'] - name: Verify the existing edge configuration before changing documentation ansible.builtin.command: nginx -t changed_when: false - name: Name the immutable documentation release ansible.builtin.set_fact: documentation_release: '/opt/pharos-documentation/releases/{{ documentation_revision }}-{{ documentation_sha256[:12] }}' - name: Prepare the documentation directories ansible.builtin.file: path: '{{ item }}' state: directory owner: root group: root mode: '0755' loop: - /opt/pharos-documentation - /opt/pharos-documentation/releases - /opt/pharos-documentation/incoming - /opt/pharos-documentation/tls - '{{ documentation_release }}' - name: Copy the verified public artifact ansible.builtin.copy: src: '{{ documentation_archive }}' dest: '/opt/pharos-documentation/incoming/{{ documentation_sha256 }}.tar.gz' owner: root group: root mode: '0644' - name: Verify the transferred archive ansible.builtin.command: argv: [sha256sum, '/opt/pharos-documentation/incoming/{{ documentation_sha256 }}.tar.gz'] register: documentation_transferred changed_when: false failed_when: documentation_transferred.rc != 0 or documentation_transferred.stdout.split()[0] != documentation_sha256 - name: Unpack a new release or recover an interrupted unpack ansible.builtin.unarchive: src: '/opt/pharos-documentation/incoming/{{ documentation_sha256 }}.tar.gz' dest: '{{ documentation_release }}' remote_src: true creates: '{{ documentation_release }}/.complete' - name: Verify every published file before activation ansible.builtin.command: argv: [sha256sum, --quiet, --check, .documentation-manifest.sha256] chdir: '{{ documentation_release }}' changed_when: false - name: Read the delivered build identity ansible.builtin.slurp: src: '{{ documentation_release }}/build-info.json' register: documentation_build - name: Require the exact clean source revision ansible.builtin.assert: that: - (documentation_build.content | b64decode | from_json).revision == documentation_revision - not (documentation_build.content | b64decode | from_json).dirty - name: Make public static files readable by the existing edge worker ansible.builtin.file: path: '{{ documentation_release }}' recurse: true owner: root group: root mode: u=rwX,go=rX - name: Mark the fully verified release ansible.builtin.copy: content: '{{ documentation_sha256 }}' dest: '{{ documentation_release }}/.complete' owner: root group: root mode: '0644' - name: Prepare documentation TLS with the existing certificate owner ansible.builtin.include_tasks: documentation-certificate.yml - name: Label static documentation for the Fedora edge when: ansible_facts.os_family == 'RedHat' block: - name: Register the documentation SELinux file contexts ansible.builtin.command: argv: [semanage, fcontext, -a, -t, '{{ item.type }}', '{{ item.path }}'] loop: - {type: httpd_sys_content_t, path: '/opt/pharos-documentation/releases(/.*)?'} - {type: httpd_config_t, path: '/opt/pharos-documentation/tls(/.*)?'} register: documentation_labels changed_when: documentation_labels.rc == 0 failed_when: documentation_labels.rc != 0 and 'already defined' not in documentation_labels.stderr environment: {LC_ALL: C} - name: Apply registered documentation labels ansible.builtin.command: restorecon -RF /opt/pharos-documentation changed_when: false - name: Check ownership of the documentation release pointer ansible.builtin.stat: path: /opt/pharos-documentation/current follow: false register: documentation_pointer - name: Preserve unexpected files at the release pointer ansible.builtin.assert: that: not documentation_pointer.stat.exists or documentation_pointer.stat.islnk | default(false) - name: Read the previous documentation release ansible.builtin.command: readlink /opt/pharos-documentation/current register: documentation_previous changed_when: false failed_when: documentation_previous.rc not in [0, 1] - name: Require documentation ownership of the previous target ansible.builtin.assert: that: documentation_previous.rc == 1 or documentation_previous.stdout is match('^/opt/pharos-documentation/releases/[0-9a-f-]+$') - name: Check the previous documentation configuration ansible.builtin.stat: path: /etc/nginx/conf.d/pharos-documentation.conf register: documentation_previous_config - name: Retain the previous configuration for rollback ansible.builtin.slurp: src: /etc/nginx/conf.d/pharos-documentation.conf register: documentation_saved_config when: documentation_previous_config.stat.exists - name: Activate and verify documentation with rollback on failure block: - name: Install the documentation virtual host ansible.builtin.template: src: documentation.conf.j2 dest: /etc/nginx/conf.d/pharos-documentation.conf owner: root group: root mode: '0644' register: documentation_nginx - name: Validate the complete Nginx configuration ansible.builtin.command: nginx -t changed_when: false - name: Switch the static release atomically ansible.builtin.command: argv: - python3 - -c - 'import os,sys,uuid; target=sys.argv[1]; current="/opt/pharos-documentation/current"; temporary=current+"-"+uuid.uuid4().hex; os.symlink(target,temporary); os.replace(temporary,current)' - '{{ documentation_release }}' when: documentation_previous.stdout != documentation_release register: documentation_switched changed_when: true - name: Reload the existing edge ansible.builtin.systemd: name: nginx state: reloaded when: documentation_nginx.changed or documentation_switched.changed or documentation_tls_changed - name: Read the documentation through verified HTTPS ansible.builtin.command: argv: >- {{ ['curl', '--fail', '--silent', '--show-error', '--noproxy', '*', '--connect-timeout', '5', '--max-time', '15', '--connect-to', documentation_hostname + ':443:127.0.0.1:' + ('8444' if documentation_mode == 'development' else '443')] + (['--cacert', edge_tls + '/ca.crt'] if documentation_mode == 'development' else []) + ['https://' + documentation_hostname + '/build-info.json'] }} register: documentation_https changed_when: false retries: 5 delay: 1 until: documentation_https.rc == 0 and (documentation_https.stdout | from_json).revision == documentation_revision - name: Confirm the served revision ansible.builtin.assert: that: - (documentation_https.stdout | from_json).revision == documentation_revision - not (documentation_https.stdout | from_json).dirty rescue: - name: Restore the previous release pointer ansible.builtin.command: argv: - python3 - -c - 'import os,sys,uuid; current="/opt/pharos-documentation/current"; target=sys.argv[1]; temporary=current+"-"+uuid.uuid4().hex; os.symlink(target,temporary) if target else None; os.replace(temporary,current) if target else (os.unlink(current) if os.path.islink(current) else None)' - '{{ documentation_previous.stdout }}' changed_when: true - name: Restore the previous documentation configuration ansible.builtin.copy: content: '{{ documentation_saved_config.content | b64decode }}' dest: /etc/nginx/conf.d/pharos-documentation.conf owner: root group: root mode: '0644' when: documentation_previous_config.stat.exists - name: Remove an unqualified first installation ansible.builtin.file: path: /etc/nginx/conf.d/pharos-documentation.conf state: absent when: not documentation_previous_config.stat.exists - name: Validate the restored edge ansible.builtin.command: nginx -t changed_when: false - name: Reload the restored edge ansible.builtin.systemd: {name: nginx, state: reloaded} - name: Report the failed documentation activation ansible.builtin.fail: msg: Documentation activation failed; the previous documentation configuration was restored.