GuestOps/deploy/ansible/guestops.yml

293 lines
9.7 KiB
YAML

---
- name: Install a verified GuestOps source release
hosts: guestops
become: true
gather_facts: true
vars:
guestops_root: /opt/guestops
guestops_config_root: /etc/guestops
guestops_release_root: "{{ guestops_root }}/releases/{{ guestops_release_commit }}"
guestops_staging_archive: "/var/tmp/{{ guestops_archive | basename }}"
guestops_staging_record: "/var/tmp/{{ guestops_source_record | basename }}"
guestops_api_image: "guestops-api:{{ guestops_release_commit }}"
guestops_worker_image: "guestops-worker:{{ guestops_release_commit }}"
pre_tasks:
- name: Validate required release variables
ansible.builtin.assert:
that:
- guestops_release_version is match('^[0-9]+\\.[0-9]+\\.[0-9]+$')
- guestops_release_commit is match('^[0-9a-f]{40}$')
- guestops_archive_sha256 is match('^[0-9a-f]{64}$')
- guestops_archive | length > 0
- guestops_source_record | length > 0
- guestops_evidence_directory | length > 0
fail_msg: Release version, full commit, archive, SHA-256, source record and evidence directory are required.
- name: Verify source package on the Ansible controller
ansible.builtin.command:
argv:
- python3
- "{{ playbook_dir }}/../verify_source_package.py"
- --archive
- "{{ guestops_archive }}"
- --record
- "{{ guestops_source_record }}"
- --expected-commit
- "{{ guestops_release_commit }}"
- --expected-version
- "{{ guestops_release_version }}"
delegate_to: localhost
become: false
changed_when: false
- name: Check private GuestOps environment exists
ansible.builtin.stat:
path: "{{ guestops_config_root }}/guestops.env"
register: guestops_environment
- name: Require a private pre-provisioned environment file
ansible.builtin.assert:
that:
- guestops_environment.stat.exists
- guestops_environment.stat.isreg
- guestops_environment.stat.mode == '0600'
fail_msg: /etc/guestops/guestops.env must already exist with mode 0600.
tasks:
- name: Create release and evidence directories
ansible.builtin.file:
path: "{{ item.path }}"
state: directory
owner: root
group: root
mode: "{{ item.mode }}"
loop:
- { path: "{{ guestops_root }}/releases", mode: "0755" }
- { path: "{{ guestops_release_root }}", mode: "0755" }
- { path: "{{ guestops_config_root }}/release-records", mode: "0700" }
- name: Copy source archive to the host
ansible.builtin.copy:
src: "{{ guestops_archive }}"
dest: "{{ guestops_staging_archive }}"
owner: root
group: root
mode: "0600"
- name: Copy source record to the host
ansible.builtin.copy:
src: "{{ guestops_source_record }}"
dest: "{{ guestops_staging_record }}"
owner: root
group: root
mode: "0600"
- name: Calculate transferred archive checksum
ansible.builtin.stat:
path: "{{ guestops_staging_archive }}"
checksum_algorithm: sha256
register: guestops_transferred_archive
- name: Reject a changed transferred archive
ansible.builtin.assert:
that:
- guestops_transferred_archive.stat.checksum == guestops_archive_sha256
fail_msg: Transferred GuestOps archive does not match the approved SHA-256.
- name: Extract committed application files
ansible.builtin.unarchive:
src: "{{ guestops_staging_archive }}"
dest: "{{ guestops_release_root }}"
remote_src: true
extra_opts:
- --strip-components=1
- name: Verify transferred package and record on the target
ansible.builtin.command:
argv:
- python3
- deploy/verify_source_package.py
- --archive
- "{{ guestops_staging_archive }}"
- --record
- "{{ guestops_staging_record }}"
- --expected-commit
- "{{ guestops_release_commit }}"
- --expected-version
- "{{ guestops_release_version }}"
- --output
- "{{ guestops_config_root }}/release-records/{{ guestops_release_commit }}.source-verification.json"
chdir: "{{ guestops_release_root }}"
changed_when: false
- name: Retain approved source record on the target
ansible.builtin.copy:
src: "{{ guestops_staging_record }}"
dest: "{{ guestops_config_root }}/release-records/{{ guestops_release_commit }}.source.json"
remote_src: true
owner: root
group: root
mode: "0600"
- name: Select release images and safe pilot defaults in the private environment
ansible.builtin.lineinfile:
path: "{{ guestops_config_root }}/guestops.env"
regexp: "^{{ item.name }}="
line: "{{ item.name }}={{ item.value }}"
create: false
mode: "0600"
loop:
- { name: GUESTOPS_API_IMAGE, value: "{{ guestops_api_image }}" }
- { name: GUESTOPS_WORKER_IMAGE, value: "{{ guestops_worker_image }}" }
- { name: GOOGLE_ENABLE_SENDING, value: "false" }
- { name: AUTO_REPLY_ENABLE_LIVE, value: "false" }
no_log: true
- name: Copy private deployment environment into the release
ansible.builtin.copy:
src: "{{ guestops_config_root }}/guestops.env"
dest: "{{ guestops_release_root }}/.env"
remote_src: true
owner: root
group: root
mode: "0600"
- name: Build API image from the verified source
ansible.builtin.command:
argv:
- docker
- build
- --target
- api
- --tag
- "{{ guestops_api_image }}"
- .
chdir: "{{ guestops_release_root }}"
register: guestops_api_build
changed_when: true
- name: Build worker image from the verified source
ansible.builtin.command:
argv:
- docker
- build
- --target
- worker
- --tag
- "{{ guestops_worker_image }}"
- .
chdir: "{{ guestops_release_root }}"
register: guestops_worker_build
changed_when: true
- name: Inspect API image identity
ansible.builtin.command:
argv: [docker, image, inspect, --format, "{{ '{{.Id}}' }}", "{{ guestops_api_image }}"]
register: guestops_api_identity
changed_when: false
- name: Inspect worker image identity
ansible.builtin.command:
argv: [docker, image, inspect, --format, "{{ '{{.Id}}' }}", "{{ guestops_worker_image }}"]
register: guestops_worker_identity
changed_when: false
- name: Create post-build release record
ansible.builtin.command:
argv:
- python3
- deploy/release_record.py
- --artifact
- "{{ guestops_staging_archive }}"
- --commit
- "{{ guestops_release_commit }}"
- --api-image
- "{{ guestops_api_image }}"
- --api-id
- "{{ guestops_api_identity.stdout }}"
- --worker-image
- "{{ guestops_worker_image }}"
- --worker-id
- "{{ guestops_worker_identity.stdout }}"
- --output
- "{{ guestops_config_root }}/release-records/{{ guestops_release_commit }}.json"
chdir: "{{ guestops_release_root }}"
- name: Run offline deployment preflight
ansible.builtin.command:
argv:
- python3
- deploy/ops.py
- preflight
- --offline
chdir: "{{ guestops_release_root }}"
changed_when: false
- name: Validate Compose without exposing expanded configuration
ansible.builtin.command:
argv:
- docker
- compose
- --env-file
- "{{ guestops_config_root }}/guestops.env"
- config
- --quiet
chdir: "{{ guestops_release_root }}"
changed_when: false
- name: Start the verified release without rebuilding
ansible.builtin.command:
argv:
- docker
- compose
- --env-file
- "{{ guestops_config_root }}/guestops.env"
- up
- --detach
- --no-build
chdir: "{{ guestops_release_root }}"
environment:
GUESTOPS_API_IMAGE: "{{ guestops_api_image }}"
GUESTOPS_WORKER_IMAGE: "{{ guestops_worker_image }}"
changed_when: true
- name: Wait for loopback readiness
ansible.builtin.uri:
url: http://127.0.0.1:8080/health/ready
method: GET
status_code: 200
return_content: false
register: guestops_readiness
retries: 30
delay: 2
until: guestops_readiness.status == 200
- name: Select the current successful release
ansible.builtin.file:
src: "{{ guestops_release_root }}"
dest: "{{ guestops_root }}/current"
state: link
force: true
- name: Retain release record on the Ansible controller
ansible.builtin.fetch:
src: "{{ guestops_config_root }}/release-records/{{ guestops_release_commit }}.json"
dest: "{{ guestops_evidence_directory }}/release-record.json"
flat: true
- name: Retain target source verification on the Ansible controller
ansible.builtin.fetch:
src: "{{ guestops_config_root }}/release-records/{{ guestops_release_commit }}.source-verification.json"
dest: "{{ guestops_evidence_directory }}/source-verification.json"
flat: true
- name: Remove transferred staging files after successful deployment
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- "{{ guestops_staging_archive }}"
- "{{ guestops_staging_record }}"