From 15dd0a27219e13daf212363146dd2efd1dd7d485 Mon Sep 17 00:00:00 2001 From: Kevin Jilissen Date: Mon, 9 Sep 2024 21:36:17 +0200 Subject: [PATCH 1/6] Define Docker installation ansible role --- .../files/install-docker/.gitignore | 1 + .../ansible/roles/docker/files/docker.asc | 62 +++++++++++++++++++ .../ansible/roles/docker/tasks/main.yml | 24 +++++++ .../ansible/roles/docker/vars/.gitignore | 1 + 4 files changed, 88 insertions(+) create mode 100644 provision-contest/ansible/roles/base_packages/files/install-docker/.gitignore create mode 100644 provision-contest/ansible/roles/docker/files/docker.asc create mode 100644 provision-contest/ansible/roles/docker/tasks/main.yml create mode 100644 provision-contest/ansible/roles/docker/vars/.gitignore diff --git a/provision-contest/ansible/roles/base_packages/files/install-docker/.gitignore b/provision-contest/ansible/roles/base_packages/files/install-docker/.gitignore new file mode 100644 index 00000000..c00df136 --- /dev/null +++ b/provision-contest/ansible/roles/base_packages/files/install-docker/.gitignore @@ -0,0 +1 @@ +*.deb diff --git a/provision-contest/ansible/roles/docker/files/docker.asc b/provision-contest/ansible/roles/docker/files/docker.asc new file mode 100644 index 00000000..ee7872e5 --- /dev/null +++ b/provision-contest/ansible/roles/docker/files/docker.asc @@ -0,0 +1,62 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +YT90qFF93M3v01BbxP+EIY2/9tiIPbrd +=0YYh +-----END PGP PUBLIC KEY BLOCK----- diff --git a/provision-contest/ansible/roles/docker/tasks/main.yml b/provision-contest/ansible/roles/docker/tasks/main.yml new file mode 100644 index 00000000..aaa2a839 --- /dev/null +++ b/provision-contest/ansible/roles/docker/tasks/main.yml @@ -0,0 +1,24 @@ +--- +- name: Add Docker GPG apt Key + apt_key: + data: "{{ lookup('ansible.builtin.file', 'docker.asc') }}" + state: present + +- name: Add Docker Repository + when: not WF_RESTRICTED_NETWORK + apt_repository: + repo: deb https://download.docker.com/linux/ubuntu jammy stable + state: present + +- name: Install Docker and dependencies + apt: + state: present + install_recommends: false + pkg: + - containerd.io + - docker-buildx-plugin + - docker-ce + - docker-ce-cli + - docker-compose-plugin + - python3-pip + - python3-docker diff --git a/provision-contest/ansible/roles/docker/vars/.gitignore b/provision-contest/ansible/roles/docker/vars/.gitignore new file mode 100644 index 00000000..1cda54be --- /dev/null +++ b/provision-contest/ansible/roles/docker/vars/.gitignore @@ -0,0 +1 @@ +*.yml From 7f210cdaa49e366bb159ea1d4b7e696e35e44d04 Mon Sep 17 00:00:00 2001 From: Kevin Jilissen Date: Sun, 6 Oct 2024 13:02:57 +0200 Subject: [PATCH 2/6] Define task to load docker containers from archive --- provision-contest/ansible/README.md | 1 + .../ansible/roles/docker/files/README.md | 5 +++ .../roles/docker/tasks/load-container.yml | 35 +++++++++++++++++++ .../ansible/roles/docker/tasks/main.yml | 6 ++++ 4 files changed, 47 insertions(+) create mode 100644 provision-contest/ansible/roles/docker/files/README.md create mode 100644 provision-contest/ansible/roles/docker/tasks/load-container.yml diff --git a/provision-contest/ansible/README.md b/provision-contest/ansible/README.md index 41c2fa47..b8493c8d 100644 --- a/provision-contest/ansible/README.md +++ b/provision-contest/ansible/README.md @@ -44,6 +44,7 @@ There are a few places where additional files should/can be added: * Machine/group specific local packages under `roles/base_packages/files/install-*/`. * Judgehost chroot local packages under `roles/judgedaemon/files/install-chroot/`. * The vendor dependencies under `roles/domjudge_checkout/files/vendor.tgz`. +* Machine/group specific docker containers under `roles/docker/files/containers-*/`. ## TODO diff --git a/provision-contest/ansible/roles/docker/files/README.md b/provision-contest/ansible/roles/docker/files/README.md new file mode 100644 index 00000000..2eafe9e8 --- /dev/null +++ b/provision-contest/ansible/roles/docker/files/README.md @@ -0,0 +1,5 @@ +# Loading containers from archives +Any container `.tar` files placed in a `containers-` directory will be loaded as a container for the said host type. +The container will be tagged with the relative path starting from `containers-`, without the `.tar` file extension. +For example, the file `containers-glitchtip/glitchtip/glitchtip:v4.1.3.tar` will be loaded as a container tagged `glitchtip/glitchtip:v4.1.3` for the `glitchtip` host type. +Note that a nested directory structure is needed to tag containers which have both an organization and a container name. diff --git a/provision-contest/ansible/roles/docker/tasks/load-container.yml b/provision-contest/ansible/roles/docker/tasks/load-container.yml new file mode 100644 index 00000000..1f4c6df2 --- /dev/null +++ b/provision-contest/ansible/roles/docker/tasks/load-container.yml @@ -0,0 +1,35 @@ +--- +- name: Load the container from archive if needed + block: + - name: Check for existing container + community.docker.docker_image_info: + name: "{{ img_name }}" + register: result + + - name: Transfer and load the container + block: + - name: Create temp container directory + file: + path: /tmp/dj_ansible + state: directory + owner: root + group: root + mode: 0700 + + - name: Transfer container archive + copy: + src: "{{ item.src }}" + dest: "{{ img_path }}" + owner: root + group: root + mode: 0700 + + - name: Import container from archive + community.docker.docker_image: + name: "{{ img_name }}" + load_path: "{{ img_path }}" + source: load + when: not result.images + vars: + img_name: "{{ item.path | splitext | first }}" + img_path: "/tmp/dj_ansible/{{ item.path | basename }}" diff --git a/provision-contest/ansible/roles/docker/tasks/main.yml b/provision-contest/ansible/roles/docker/tasks/main.yml index aaa2a839..632d7d93 100644 --- a/provision-contest/ansible/roles/docker/tasks/main.yml +++ b/provision-contest/ansible/roles/docker/tasks/main.yml @@ -22,3 +22,9 @@ - docker-compose-plugin - python3-pip - python3-docker + +- name: Load container archives + include_tasks: load-container.yml + with_filetree: + - files/containers-{{ host_type }}/ + when: item.state == 'file' and (item.path | splitext | last) == ".tar" From 7f703150d25bcf86f6d7bc2aa6336f9d4e3ce646 Mon Sep 17 00:00:00 2001 From: Kevin Jilissen Date: Sun, 6 Oct 2024 13:06:51 +0200 Subject: [PATCH 3/6] Define GlitchTip installation role In a later restructure of Ansible, the GlithTip role should become part of the admin role, just like the Grafana roles has to be merged in. Use the reusable Docker definitions to install Docker. In the compose stack, use the host CA bundle for a.o. uptime monitor as often custom certs are used on contest floors which must be trusted in the container. --- provision-contest/ansible/glitchtip.yml | 21 +++++++ .../group_vars/onprem/secret.yml.example | 3 + provision-contest/ansible/hosts.example | 7 ++- .../files/containers-glitchtip/.gitignore | 1 + .../containers-glitchtip/glitchtip/.gitignore | 1 + .../ansible/roles/glitchtip/defaults/main.yml | 1 + .../ansible/roles/glitchtip/handlers/main.yml | 7 +++ .../ansible/roles/glitchtip/tasks/main.yml | 17 ++++++ .../templates/docker-compose.yaml.j2 | 57 +++++++++++++++++++ .../ansible/roles/glitchtip/vars/.gitignore | 1 + 10 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 provision-contest/ansible/glitchtip.yml create mode 100644 provision-contest/ansible/roles/docker/files/containers-glitchtip/.gitignore create mode 100644 provision-contest/ansible/roles/docker/files/containers-glitchtip/glitchtip/.gitignore create mode 100644 provision-contest/ansible/roles/glitchtip/defaults/main.yml create mode 100644 provision-contest/ansible/roles/glitchtip/handlers/main.yml create mode 100644 provision-contest/ansible/roles/glitchtip/tasks/main.yml create mode 100644 provision-contest/ansible/roles/glitchtip/templates/docker-compose.yaml.j2 create mode 100644 provision-contest/ansible/roles/glitchtip/vars/.gitignore diff --git a/provision-contest/ansible/glitchtip.yml b/provision-contest/ansible/glitchtip.yml new file mode 100644 index 00000000..a9673f7c --- /dev/null +++ b/provision-contest/ansible/glitchtip.yml @@ -0,0 +1,21 @@ +--- +- hosts: glitchtip + vars: + host_type: glitchtip + become: true + handlers: + - include_tasks: handlers.yml + roles: + - role: base_packages + tags: base_packages + - role: icpc_fixes + tags: icpc_fixes + when: ICPC_IMAGE + - role: system_fixes + tags: system_fixes + - role: hosts + tags: hosts + - role: docker + tags: docker + - role: glitchtip + tags: glitchtip diff --git a/provision-contest/ansible/group_vars/onprem/secret.yml.example b/provision-contest/ansible/group_vars/onprem/secret.yml.example index 8e86e0ef..c73b8be0 100644 --- a/provision-contest/ansible/group_vars/onprem/secret.yml.example +++ b/provision-contest/ansible/group_vars/onprem/secret.yml.example @@ -69,3 +69,6 @@ PRESCLIENT_CONTEST: nwerc18 # Sentry DSN URL # SENTRY_DSN: + +# GlitchTip +# GLITCHTIP_SECRET: {some-strong-glitchtip-password} diff --git a/provision-contest/ansible/hosts.example b/provision-contest/ansible/hosts.example index b0f9cddb..837eb59e 100644 --- a/provision-contest/ansible/hosts.example +++ b/provision-contest/ansible/hosts.example @@ -61,7 +61,12 @@ domjudge-ccsadmin5 ansible_host=10.3.3.227 [grafana] # During the WFs we use one of the ccsadmin machines # Doesn't matter which (admin) machine but should not be 1 as that runs ansible -domjudge-ccsadmin2 ansible_host=10.3.3.225 +domjudge-ccsadmin2 ansible_host=10.3.3.224 + +[glitchtip] +# During the WFs we use one of the ccsadmin machines +# Doesn't matter which (admin) machine but should not be 1 as that runs ansible +domjudge-ccsadmin3 ansible_host=10.3.3.225 [cds] domjudge-cds ansible_host=10.2.2.228 diff --git a/provision-contest/ansible/roles/docker/files/containers-glitchtip/.gitignore b/provision-contest/ansible/roles/docker/files/containers-glitchtip/.gitignore new file mode 100644 index 00000000..d874ad67 --- /dev/null +++ b/provision-contest/ansible/roles/docker/files/containers-glitchtip/.gitignore @@ -0,0 +1 @@ +*.tar diff --git a/provision-contest/ansible/roles/docker/files/containers-glitchtip/glitchtip/.gitignore b/provision-contest/ansible/roles/docker/files/containers-glitchtip/glitchtip/.gitignore new file mode 100644 index 00000000..72e8ffc0 --- /dev/null +++ b/provision-contest/ansible/roles/docker/files/containers-glitchtip/glitchtip/.gitignore @@ -0,0 +1 @@ +* diff --git a/provision-contest/ansible/roles/glitchtip/defaults/main.yml b/provision-contest/ansible/roles/glitchtip/defaults/main.yml new file mode 100644 index 00000000..b09e4f05 --- /dev/null +++ b/provision-contest/ansible/roles/glitchtip/defaults/main.yml @@ -0,0 +1 @@ +GLITCHTIP_PORT: 8000 diff --git a/provision-contest/ansible/roles/glitchtip/handlers/main.yml b/provision-contest/ansible/roles/glitchtip/handlers/main.yml new file mode 100644 index 00000000..61571dfd --- /dev/null +++ b/provision-contest/ansible/roles/glitchtip/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: Restart GlitchTip + community.docker.docker_compose_v2: + project_src: /opt/glitchtip + files: + - docker-compose.yaml + state: restarted diff --git a/provision-contest/ansible/roles/glitchtip/tasks/main.yml b/provision-contest/ansible/roles/glitchtip/tasks/main.yml new file mode 100644 index 00000000..ddc85269 --- /dev/null +++ b/provision-contest/ansible/roles/glitchtip/tasks/main.yml @@ -0,0 +1,17 @@ +--- +- name: Create directories + become_user: root + file: + path: /opt/glitchtip + state: directory + +- name: Create compose file + template: + src: templates/docker-compose.yaml.j2 + dest: /opt/glitchtip/docker-compose.yaml + +- name: Deploy GlitchTip compose stack + community.docker.docker_compose_v2: + project_src: /opt/glitchtip + files: + - docker-compose.yaml diff --git a/provision-contest/ansible/roles/glitchtip/templates/docker-compose.yaml.j2 b/provision-contest/ansible/roles/glitchtip/templates/docker-compose.yaml.j2 new file mode 100644 index 00000000..d53dca42 --- /dev/null +++ b/provision-contest/ansible/roles/glitchtip/templates/docker-compose.yaml.j2 @@ -0,0 +1,57 @@ +# Uncomment version if using an older version of docker compose +# version: "3.8" +x-environment: + &default-environment + DATABASE_URL: postgres://postgres:postgres@postgres:5432/postgres + SECRET_KEY: {{ GLITCHTIP_SECRET }} + PORT: {{ GLITCHTIP_PORT}} + EMAIL_URL: consolemail:// + GLITCHTIP_DOMAIN: http://glitchtip:{{ GLITCHTIP_PORT }} + DEFAULT_FROM_EMAIL: email@glitchtip + CELERY_WORKER_AUTOSCALE: "1,1" + CELERY_WORKER_MAX_TASKS_PER_CHILD: "1000" + REQUESTS_CA_BUNDLE: /etc/ssl/certs/ca-certificates.crt + +x-depends_on: + &default-depends_on + - postgres + - redis + +services: + postgres: + image: postgres:16-alpine + environment: + POSTGRES_HOST_AUTH_METHOD: "trust" # Consider removing this and setting a password + restart: unless-stopped + volumes: + - pg-data:/var/lib/postgresql/data + redis: + image: redis:7-alpine + restart: unless-stopped + web: + image: glitchtip/glitchtip:v4.1.3 + depends_on: *default-depends_on + ports: + - {{ GLITCHTIP_PORT }}:{{ GLITCHTIP_PORT }} + environment: *default-environment + restart: unless-stopped + volumes: + - uploads:/code/uploads + worker: + image: glitchtip/glitchtip:v4.1.3 + command: ./bin/run-celery-with-beat.sh + depends_on: *default-depends_on + environment: *default-environment + restart: unless-stopped + volumes: + - uploads:/code/uploads + - /etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt:ro + migrate: + image: glitchtip/glitchtip:v4.1.3 + depends_on: *default-depends_on + command: ./bin/run-migrate.sh + environment: *default-environment + +volumes: + pg-data: + uploads: diff --git a/provision-contest/ansible/roles/glitchtip/vars/.gitignore b/provision-contest/ansible/roles/glitchtip/vars/.gitignore new file mode 100644 index 00000000..1cda54be --- /dev/null +++ b/provision-contest/ansible/roles/glitchtip/vars/.gitignore @@ -0,0 +1 @@ +*.yml From 6e01b6994f164847aa3397bef6ad69ad1fda4c2f Mon Sep 17 00:00:00 2001 From: Kevin Jilissen Date: Sun, 6 Oct 2024 13:11:17 +0200 Subject: [PATCH 4/6] Remove executable bit from "/usr/local/bin/runc" The runc script interferes with running Docker containers. --- provision-contest/ansible/roles/docker/tasks/main.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/provision-contest/ansible/roles/docker/tasks/main.yml b/provision-contest/ansible/roles/docker/tasks/main.yml index 632d7d93..537c7304 100644 --- a/provision-contest/ansible/roles/docker/tasks/main.yml +++ b/provision-contest/ansible/roles/docker/tasks/main.yml @@ -23,6 +23,15 @@ - python3-pip - python3-docker +# The `runc` script interferes with running Docker containers. +# Mark it as not executable such that containers can run normally. +# If the machine does need the `runc` in the image, a custom fix has to be applied. +- name: Remove executable bit from "/usr/local/bin/runc" + when: ICPC_IMAGE + file: + dest: /usr/local/bin/runc + mode: -x + - name: Load container archives include_tasks: load-container.yml with_filetree: From 66a0c2b4a32379fd591c86606be2caf89c60a2d3 Mon Sep 17 00:00:00 2001 From: Kevin Jilissen Date: Wed, 18 Sep 2024 18:03:39 +0500 Subject: [PATCH 5/6] Provision GlitchTip configuration Use the GlitchTip API to provision some default configuration based on the defined hosts in Ansible. --- .../group_vars/onprem/secret.yml.example | 1 + .../ansible/roles/glitchtip/defaults/main.yml | 1 + .../roles/glitchtip/tasks/create-monitor.yml | 30 ++++ .../ansible/roles/glitchtip/tasks/main.yml | 150 +++++++++++++++++- .../templates/docker-compose.yaml.j2 | 2 +- 5 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 provision-contest/ansible/roles/glitchtip/tasks/create-monitor.yml diff --git a/provision-contest/ansible/group_vars/onprem/secret.yml.example b/provision-contest/ansible/group_vars/onprem/secret.yml.example index c73b8be0..bdd179a5 100644 --- a/provision-contest/ansible/group_vars/onprem/secret.yml.example +++ b/provision-contest/ansible/group_vars/onprem/secret.yml.example @@ -72,3 +72,4 @@ PRESCLIENT_CONTEST: nwerc18 # GlitchTip # GLITCHTIP_SECRET: {some-strong-glitchtip-password} +# GLITCHTIP_PASSWORD: {some-strong-glitchtip-password} diff --git a/provision-contest/ansible/roles/glitchtip/defaults/main.yml b/provision-contest/ansible/roles/glitchtip/defaults/main.yml index b09e4f05..52c2ae7c 100644 --- a/provision-contest/ansible/roles/glitchtip/defaults/main.yml +++ b/provision-contest/ansible/roles/glitchtip/defaults/main.yml @@ -1 +1,2 @@ GLITCHTIP_PORT: 8000 +GLITCHTIP_TOKEN: diff --git a/provision-contest/ansible/roles/glitchtip/tasks/create-monitor.yml b/provision-contest/ansible/roles/glitchtip/tasks/create-monitor.yml new file mode 100644 index 00000000..c8830a5a --- /dev/null +++ b/provision-contest/ansible/roles/glitchtip/tasks/create-monitor.yml @@ -0,0 +1,30 @@ +--- +- name: Fetch the project id + ansible.builtin.uri: + method: GET + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/teams/domjudge/DOMjudge/projects/" + headers: + Authorization: "Bearer {{ GLITCHTIP_TOKEN }}" + register: glitchtip_proj +- name: Store JSON query in a fact due to escaping problems in the string below + set_fact: + TEMP_QUERY0: "[?name=='{{ item }}'].id" +- name: Create an DOMjudge uptime monitor for the project + ansible.builtin.uri: + method: POST + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/organizations/domjudge/monitors/" + status_code: 201 + headers: + Authorization: "Bearer {{ GLITCHTIP_TOKEN }}" + body: + expectedBody: "" + expectedStatus: 200 + timeout: 5 + interval: 2 + monitorType: "GET" + url: "https://{{ hostvars[item].ansible_host }}/public" + name: "{{ item }}" + project_id: "{{ glitchtip_proj.json | community.general.json_query(TEMP_QUERY0) | first }}" + body_format: json diff --git a/provision-contest/ansible/roles/glitchtip/tasks/main.yml b/provision-contest/ansible/roles/glitchtip/tasks/main.yml index ddc85269..f4b1c5e5 100644 --- a/provision-contest/ansible/roles/glitchtip/tasks/main.yml +++ b/provision-contest/ansible/roles/glitchtip/tasks/main.yml @@ -1,6 +1,5 @@ --- - name: Create directories - become_user: root file: path: /opt/glitchtip state: directory @@ -15,3 +14,152 @@ project_src: /opt/glitchtip files: - docker-compose.yaml + +- name: Assume we don't have an account if we didn't specify the token + when: not GLITCHTIP_TOKEN + block: + - name: Wait for stable GlitchTip migrations + ansible.builtin.wait_for: + timeout: 10 + + - name: Fetch CSRF from login page + ansible.builtin.uri: + method: GET + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/docs" + register: glitchtip_csrf + + - name: Register DOMjudge account + ansible.builtin.uri: + method: POST + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/_allauth/browser/v1/auth/signup" + status_code: 200 + headers: + Cookie: "{{ glitchtip_csrf.cookies_string }}" + X-CSRFTOKEN: "{{ glitchtip_csrf.cookies_string | regex_search('csrftoken=([a-zA-Z0-9]+)', '\\1') | first }}" + body: + email: "team@domjudge.org" + password: "{{ GLITCHTIP_PASSWORD }}" + body_format: json + register: glitchtip_register + + - name: Create API token + ansible.builtin.uri: + method: POST + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/api-tokens/" + status_code: 201 + headers: + Cookie: "{{ glitchtip_register.cookies_string }}" + X-CSRFTOKEN: "{{ glitchtip_register.cookies_string | regex_search('csrftoken=([a-zA-Z0-9]+)', '\\1') | first }}" + body: + label: "ansible" + scopes: [ + "project:read", + "project:write", + "project:admin", + "project:releases", + "team:read", + "team:write", + "team:admin", + "event:read", + "event:write", + "event:admin", + "org:read", + "org:write", + "org:admin", + "member:read", + "member:write", + "member:admin" + ] + body_format: json + register: glitchtip_token + + - name: Set API token + ansible.builtin.set_fact: + GLITCHTIP_TOKEN: "{{ glitchtip_token.json.token }}" + +- name: Check for existing organizations + ansible.builtin.uri: + method: GET + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/organizations/" + headers: + Authorization: "Bearer {{ GLITCHTIP_TOKEN }}" + register: glitchtip_org + +- name: Create DOMjudge organization + when: glitchtip_org.json | community.general.json_query("[?name=='DOMjudge']") == [] + ansible.builtin.uri: + method: POST + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/organizations/" + status_code: 201 + headers: + Authorization: "Bearer {{ GLITCHTIP_TOKEN }}" + body: + name: "DOMjudge" + body_format: json + register: glitchtip_org + +- name: Check for existing teams + ansible.builtin.uri: + method: GET + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/organizations/domjudge/teams/" + headers: + Authorization: "Bearer {{ GLITCHTIP_TOKEN }}" + register: glitchtip_team + +- name: Create DOMjudge team + when: glitchtip_team.json | community.general.json_query("[?slug=='DOMjudge']") == [] + ansible.builtin.uri: + method: POST + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/organizations/domjudge/teams/" + status_code: 201 + headers: + Authorization: "Bearer {{ GLITCHTIP_TOKEN }}" + body: + slug: "DOMjudge" + body_format: json + register: glitchtip_team + +- name: Check for existing projects + ansible.builtin.uri: + method: GET + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/teams/domjudge/DOMjudge/projects/" + headers: + Authorization: "Bearer {{ GLITCHTIP_TOKEN }}" + register: glitchtip_proj + +- name: Create DOMjudge projects + when: glitchtip_proj.json | community.general.json_query("[?name=='{{ item }}']") == [] + ansible.builtin.uri: + method: POST + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/teams/domjudge/DOMjudge/projects/" + status_code: 201 + headers: + Authorization: "Bearer {{ GLITCHTIP_TOKEN }}" + body: + name: "{{ item }}" + platform: "php-symfony" + body_format: json + loop: "{{ ['setup-phase'] + groups['domserver'] }}" + +- name: Check for existing monitors + ansible.builtin.uri: + method: GET + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/organizations/domjudge/monitors/" + headers: + Authorization: "Bearer {{ GLITCHTIP_TOKEN }}" + register: glitchtip_mon + +- name: Create DOMjudge monitors + when: glitchtip_mon.json | community.general.json_query("[?name=='{{ item }}']") == [] + loop: "{{ groups['domserver'] }}" + include_tasks: create-monitor.yml diff --git a/provision-contest/ansible/roles/glitchtip/templates/docker-compose.yaml.j2 b/provision-contest/ansible/roles/glitchtip/templates/docker-compose.yaml.j2 index d53dca42..64e0898b 100644 --- a/provision-contest/ansible/roles/glitchtip/templates/docker-compose.yaml.j2 +++ b/provision-contest/ansible/roles/glitchtip/templates/docker-compose.yaml.j2 @@ -4,7 +4,7 @@ x-environment: &default-environment DATABASE_URL: postgres://postgres:postgres@postgres:5432/postgres SECRET_KEY: {{ GLITCHTIP_SECRET }} - PORT: {{ GLITCHTIP_PORT}} + PORT: {{ GLITCHTIP_PORT }} EMAIL_URL: consolemail:// GLITCHTIP_DOMAIN: http://glitchtip:{{ GLITCHTIP_PORT }} DEFAULT_FROM_EMAIL: email@glitchtip From 5f049ef6244c364d1fc4316f87dd00854bd44b48 Mon Sep 17 00:00:00 2001 From: Kevin Jilissen Date: Sun, 15 Sep 2024 15:01:48 +0500 Subject: [PATCH 6/6] Install dj_notify service on admin machines The dj_notify python scripts acts as a Slack webhook receiver, which forwards the notifications to the desktop notifications and plays an alert sound to notify everyone nearby. --- provision-contest/ansible/admin.yml | 2 + .../ansible/roles/dj_notify/defaults/main.yml | 3 + .../roles/dj_notify/files/dj_notify.py | 87 +++++++++++++++++++ .../ansible/roles/dj_notify/handlers/main.yml | 7 ++ .../ansible/roles/dj_notify/tasks/main.yml | 17 ++++ .../dj_notify/templates/dj_notify.service.j2 | 21 +++++ .../ansible/roles/glitchtip/defaults/main.yml | 1 + .../ansible/roles/glitchtip/tasks/main.yml | 20 +++++ 8 files changed, 158 insertions(+) create mode 100644 provision-contest/ansible/roles/dj_notify/defaults/main.yml create mode 100644 provision-contest/ansible/roles/dj_notify/files/dj_notify.py create mode 100644 provision-contest/ansible/roles/dj_notify/handlers/main.yml create mode 100644 provision-contest/ansible/roles/dj_notify/tasks/main.yml create mode 100644 provision-contest/ansible/roles/dj_notify/templates/dj_notify.service.j2 diff --git a/provision-contest/ansible/admin.yml b/provision-contest/ansible/admin.yml index f36a6f78..40a84441 100644 --- a/provision-contest/ansible/admin.yml +++ b/provision-contest/ansible/admin.yml @@ -40,6 +40,8 @@ tags: clusterssh - role: phpstorm tags: phpstorm + - role: dj_notify + tags: dj_notify - role: prometheus_target_all tags: prometheus_target_all when: GRAFANA_MONITORING diff --git a/provision-contest/ansible/roles/dj_notify/defaults/main.yml b/provision-contest/ansible/roles/dj_notify/defaults/main.yml new file mode 100644 index 00000000..1becf1d3 --- /dev/null +++ b/provision-contest/ansible/roles/dj_notify/defaults/main.yml @@ -0,0 +1,3 @@ +ALSA_DEVICE: alsa_output.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__hw_sofhdadsp__sink +NOTIFICATION_SOUND: /usr/share/sounds/sound-icons/trumpet-12.wav +NOTIFICATION_SOUND_VOLUME: 35536 diff --git a/provision-contest/ansible/roles/dj_notify/files/dj_notify.py b/provision-contest/ansible/roles/dj_notify/files/dj_notify.py new file mode 100644 index 00000000..74988427 --- /dev/null +++ b/provision-contest/ansible/roles/dj_notify/files/dj_notify.py @@ -0,0 +1,87 @@ +from http.server import BaseHTTPRequestHandler, HTTPServer +import json +import subprocess +import gi +import os +import webbrowser +import subprocess +import traceback +gi.require_version('Notify', '0.7') +from gi.repository import Notify + +HOSTNAME = "0.0.0.0" +PORT = 9999 +ALSA_DEVICE = os.environ['ALSA_DEVICE'] +NOTIFICATION_SOUND = os.environ['NOTIFICATION_SOUND'] +NOTIFICATION_SOUND_VOLUME = int(os.environ['NOTIFICATION_SOUND_VOLUME']) + + +def on_notification_closed(notification): + print(f"Notification {notification.id} closed.") + + +def on_link_click(notification, action, link): + webbrowser.open(link) + + +def filter_notification(title, body, link): + return not title.startswith("Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException") + + +class NotifyServer(BaseHTTPRequestHandler): + def create_notification(self, title, body, link): + notification = Notify.Notification.new(title, body) + notification.connect("closed", on_notification_closed) + notification.add_action( + "action_click", + "View in browser", + on_link_click, + link + ) + notification.show() + + + def notification_sound(self, sound): + # Use Popen to launch a non-blocking background process + subprocess.Popen(["paplay", "--volume", str(NOTIFICATION_SOUND_VOLUME), "--device", ALSA_DEVICE, sound]) + + + def do_POST(self): + length = int(self.headers.get('Content-Length')) + body = self.rfile.read(length) + content = json.loads(body) + print(json.dumps(content, indent=2)) + + att = content['attachments'][0] + title = att['title'] + link = att['title_link'] + body = att['text'] + + if filter_notification(title, body, link): + try: + self.create_notification(title, body, link) + except Exception: + print(traceback.format_exc()) + try: + self.notification_sound(NOTIFICATION_SOUND) + except Exception: + print(traceback.format_exc()) + + self.send_response(200) + self.send_header("Content-Type", "text/plain") + self.end_headers() + + self.wfile.write(bytes("ok", "utf-8")) + + +Notify.init("DOMjudge notifications") +server = HTTPServer((HOSTNAME, PORT), NotifyServer) + +try: + server.serve_forever() +except KeyboardInterrupt: + pass + +# Clean up +server.server_close() +Notify.uninit() diff --git a/provision-contest/ansible/roles/dj_notify/handlers/main.yml b/provision-contest/ansible/roles/dj_notify/handlers/main.yml new file mode 100644 index 00000000..b7ce19cb --- /dev/null +++ b/provision-contest/ansible/roles/dj_notify/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: Restart dj_notify + systemd: + name: dj_notify + enabled: true + state: restarted + daemon_reload: true diff --git a/provision-contest/ansible/roles/dj_notify/tasks/main.yml b/provision-contest/ansible/roles/dj_notify/tasks/main.yml new file mode 100644 index 00000000..c6391e3d --- /dev/null +++ b/provision-contest/ansible/roles/dj_notify/tasks/main.yml @@ -0,0 +1,17 @@ +--- +# These tasks install the DOMjudge Notify script + +- name: Install dj_notify + copy: + src: "dj_notify.py" + dest: "/home/domjudge/bin/dj_notify.py" + owner: domjudge + group: domjudge + mode: 0755 + notify: Restart dj_notify + +- name: Copy dj_notify systemd unit file + template: + src: "dj_notify.service.j2" + dest: "/etc/systemd/system/dj_notify.service" + notify: Restart dj_notify diff --git a/provision-contest/ansible/roles/dj_notify/templates/dj_notify.service.j2 b/provision-contest/ansible/roles/dj_notify/templates/dj_notify.service.j2 new file mode 100644 index 00000000..fb4737e2 --- /dev/null +++ b/provision-contest/ansible/roles/dj_notify/templates/dj_notify.service.j2 @@ -0,0 +1,21 @@ +[Unit] +Description="DOMjudge Notify" +After=network.target + +[Service] +Type=simple + +Environment=ALSA_DEVICE={{ ALSA_DEVICE }} +Environment=NOTIFICATION_SOUND={{ NOTIFICATION_SOUND }} +Environment=DISPLAY=:0 +Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1001/bus +Environment=PULSE_SERVER=/run/user/1001/pulse/native +WorkingDirectory=/home/domjudge +ExecStart=/usr/bin/python3 -u /home/domjudge/bin/dj_notify.py +User=domjudge + +Restart=always +RestartSec=3 + +[Install] +WantedBy=graphical.target diff --git a/provision-contest/ansible/roles/glitchtip/defaults/main.yml b/provision-contest/ansible/roles/glitchtip/defaults/main.yml index 52c2ae7c..df4a7c22 100644 --- a/provision-contest/ansible/roles/glitchtip/defaults/main.yml +++ b/provision-contest/ansible/roles/glitchtip/defaults/main.yml @@ -1,2 +1,3 @@ GLITCHTIP_PORT: 8000 GLITCHTIP_TOKEN: +GLITCHTIP_WEBHOOK_DOMAIN: domjudge-ccsadmin2 diff --git a/provision-contest/ansible/roles/glitchtip/tasks/main.yml b/provision-contest/ansible/roles/glitchtip/tasks/main.yml index f4b1c5e5..dbe16dc2 100644 --- a/provision-contest/ansible/roles/glitchtip/tasks/main.yml +++ b/provision-contest/ansible/roles/glitchtip/tasks/main.yml @@ -150,6 +150,26 @@ body_format: json loop: "{{ ['setup-phase'] + groups['domserver'] }}" +- name: Create project dj_notify webhook + when: glitchtip_proj.json | community.general.json_query("[?name=='{{ item }}']") == [] + ansible.builtin.uri: + method: POST + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/projects/domjudge/{{ item }}/alerts/" + status_code: 201 + headers: + Authorization: "Bearer {{ GLITCHTIP_TOKEN }}" + body: + name: "dj_notify" + alertRecipients: + - recipientType: "webhook" + url: "http://{{ GLITCHTIP_WEBHOOK_DOMAIN }}:9999/" + timespanMinutes: 1 + quantity: 1 + uptime: true + body_format: json + loop: "{{ ['setup-phase'] + groups['domserver'] }}" + - name: Check for existing monitors ansible.builtin.uri: method: GET