From ec028eb9d9d52d67cdd271452d02e3aff85e01d5 Mon Sep 17 00:00:00 2001 From: Alexander Cherepanov Date: Thu, 24 Aug 2023 13:12:24 +0600 Subject: [PATCH] Telegram long polling (#2250) # What this PR does Runs Telegram long polling to get updates. It's enabled by setting `FEATURE_TELEGRAM_LONG_POLLING_ENABLED=True`. That will disable webhook and run separate deployment for telegram long polling. Telegram long polling is not very HA mode, but it does not need to expose webhook url to internet and simplifies telegram integration. ## Which issue(s) this PR fixes closes #561 ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --- CHANGELOG.md | 2 + Makefile | 1 + dev/.env.dev.example | 2 +- dev/README.md | 1 + docker-compose-developer.yml | 17 +++ docs/sources/open-source/_index.md | 10 +- engine/apps/api/tests/test_live_settings.py | 30 ++++ engine/apps/base/utils.py | 4 + engine/apps/telegram/client.py | 7 + engine/apps/telegram/tasks.py | 3 + engine/engine/celery.py | 3 +- .../commands/start_telegram_polling.py | 50 ++++++ engine/settings/base.py | 1 + helm/oncall/README.md | 9 +- helm/oncall/templates/_env.tpl | 7 + .../templates/telegram-polling/_helpers.tpl | 22 +++ .../telegram-polling/deployment.yaml | 44 ++++++ ...telegram_polling_deployment_test.yaml.snap | 73 +++++++++ .../__snapshot__/wait_for_db_test.yaml.snap | 144 ++++++++++++++++++ helm/oncall/tests/extra_env_test.yaml | 11 ++ helm/oncall/tests/image_deployments_test.yaml | 4 + .../oncall/tests/image_pull_secrets_test.yaml | 3 + helm/oncall/tests/mysql_env_test.yaml | 6 + .../oncall/tests/mysql_password_env_test.yaml | 3 + helm/oncall/tests/postgres_env_test.yaml | 5 + .../tests/postgres_password_env_test.yaml | 9 ++ .../security_context_deployments_test.yaml | 4 + .../service_account_deployments_test.yaml | 5 + helm/oncall/tests/telegram_env_test.yaml | 25 +++ .../telegram_polling_deployment_test.yaml | 73 +++++++++ helm/oncall/tests/wait_for_db_test.yaml | 4 + helm/oncall/values.yaml | 11 ++ 32 files changed, 586 insertions(+), 7 deletions(-) create mode 100644 engine/engine/management/commands/start_telegram_polling.py create mode 100644 helm/oncall/templates/telegram-polling/_helpers.tpl create mode 100644 helm/oncall/templates/telegram-polling/deployment.yaml create mode 100644 helm/oncall/tests/__snapshot__/telegram_polling_deployment_test.yaml.snap create mode 100644 helm/oncall/tests/telegram_polling_deployment_test.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index b3a44fab..d7723287 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Public API for webhooks @mderynck ([#2790](https://github.com/grafana/oncall/pull/2790)) +- Use Telegram polling protocol instead of a webhook if `FEATURE_TELEGRAM_LONG_POLLING_ENABLED` set to `True` by @alexintech + ([#2250](https://github.com/grafana/oncall/pull/2250)) ### Changed diff --git a/Makefile b/Makefile index f42b3b85..14f26c0b 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ REDIS_PROFILE = redis RABBITMQ_PROFILE = rabbitmq PROMETHEUS_PROFILE = prometheus GRAFANA_PROFILE = grafana +TELEGRAM_POLLING_PROFILE = telegram_polling DEV_ENV_DIR = ./dev DEV_ENV_FILE = $(DEV_ENV_DIR)/.env.dev diff --git a/dev/.env.dev.example b/dev/.env.dev.example index 9a041dc2..1a37d99b 100644 --- a/dev/.env.dev.example +++ b/dev/.env.dev.example @@ -27,7 +27,7 @@ SOCIAL_AUTH_REDIRECT_IS_HTTPS=False GRAFANA_INCIDENT_STATIC_API_KEY= GRAFANA_API_URL=http://localhost:3000 -CELERY_WORKER_QUEUE="default,critical,long,slack,telegram,webhook,retry,celery" +CELERY_WORKER_QUEUE=default,critical,long,slack,telegram,webhook,retry,celery CELERY_WORKER_CONCURRENCY=3 CELERY_WORKER_MAX_TASKS_PER_CHILD=100 CELERY_WORKER_SHUTDOWN_INTERVAL=65m diff --git a/dev/README.md b/dev/README.md index a800b8c3..71751115 100644 --- a/dev/README.md +++ b/dev/README.md @@ -75,6 +75,7 @@ The possible profiles values are: - `rabbitmq` - `postgres` - `mysql` +- `telegram_polling` The default is `engine,oncall_ui,redis,grafana`. This runs: diff --git a/docker-compose-developer.yml b/docker-compose-developer.yml index b419b445..f6d62e8f 100644 --- a/docker-compose-developer.yml +++ b/docker-compose-developer.yml @@ -81,6 +81,23 @@ services: profiles: - engine + oncall_telegram_polling: + container_name: oncall_telegram_polling + labels: *oncall-labels + build: *oncall-build-args + restart: always + user: *oncall-user + command: sh -c "python manage.py start_telegram_polling" + env_file: *oncall-env-files + environment: *oncall-env-vars + volumes: *oncall-volumes + extra_hosts: *oncall-extra-hosts + depends_on: + oncall_db_migration: + condition: service_completed_successfully + profiles: + - telegram_polling + # used to invoke one-off commands, primarily from the Makefile # oncall_engine couldn't (easily) be used due to it's depends_on property # we could alternatively just use `docker run` however that would require diff --git a/docs/sources/open-source/_index.md b/docs/sources/open-source/_index.md index c42b8174..011b3058 100644 --- a/docs/sources/open-source/_index.md +++ b/docs/sources/open-source/_index.md @@ -178,18 +178,20 @@ frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media web-share" allowfullscreen> The Telegram integration for Grafana OnCall is designed for collaborative team work and improved incident response. +It's available in two options: using Webhooks or Long Polling. Refer to the following steps to configure the Telegram integration: 1. Ensure your Grafana OnCall environment is up and running. 2. Set `FEATURE_TELEGRAM_INTEGRATION_ENABLED` as "True" -3. Create a Telegram bot using [BotFather](https://t.me/BotFather) and save the token provided by BotFather. Please make +3. (Long Polling only) Set `FEATURE_TELEGRAM_LONG_POLLING_ENABLED` as "True" +4. Create a Telegram bot using [BotFather](https://t.me/BotFather) and save the token provided by BotFather. Please make sure to disable **Group Privacy** for the bot (Bot Settings -> Group Privacy -> Turn off). -4. Paste the token provided by BotFather to the `TELEGRAM_TOKEN` variable on the **Env Variables** page of your +5. Paste the token provided by BotFather to the `TELEGRAM_TOKEN` variable on the **Env Variables** page of your Grafana OnCall instance. -5. Set the `TELEGRAM_WEBHOOK_HOST` variable to the external address of your Grafana OnCall instance. Please note +6. (Webhook only) Set the `TELEGRAM_WEBHOOK_HOST` variable to the external address of your Grafana OnCall instance. Please note that `TELEGRAM_WEBHOOK_HOST` must start with `https://` and be publicly available (meaning that it can be reached by Telegram servers). If your host is private or local, consider using a reverse proxy (e.g. [ngrok](https://ngrok.com)). -6. Now you can connect Telegram accounts on the **Users** page and receive alert groups to Telegram direct messages. +7. Now you can connect Telegram accounts on the **Users** page and receive alert groups to Telegram direct messages. Alternatively, in case you want to connect Telegram channels to your Grafana OnCall environment, navigate to the **ChatOps** tab. diff --git a/engine/apps/api/tests/test_live_settings.py b/engine/apps/api/tests/test_live_settings.py index dce86236..db069358 100644 --- a/engine/apps/api/tests/test_live_settings.py +++ b/engine/apps/api/tests/test_live_settings.py @@ -160,3 +160,33 @@ def test_live_settings_telegram_calls_set_webhook_once( mock_set_webhook.assert_called_once_with( "TEST_UPDATED_VALUE/telegram/", allowed_updates=("message", "callback_query") ) + + +@pytest.mark.django_db +def test_live_settings_telegram_set_webhook_not_called_if_long_polling_enabled( + make_organization_and_user_with_plugin_token, + make_user_auth_headers, + make_live_setting, + settings, +): + """ + Check that when FEATURE_TELEGRAM_LONG_POLLING_ENABLED is true setting webhook with updating + TELEGRAM_WEBHOOK_HOST live setting does not evaluate. + """ + + settings.FEATURE_LIVE_SETTINGS_ENABLED = True + settings.FEATURE_TELEGRAM_LONG_POLLING_ENABLED = True + + organization, user, token = make_organization_and_user_with_plugin_token() + LiveSetting.populate_settings_if_needed() + live_setting = LiveSetting.objects.get(name="TELEGRAM_WEBHOOK_HOST") + + client = APIClient() + url = reverse("api-internal:live_settings-detail", kwargs={"pk": live_setting.public_primary_key}) + data = {"id": live_setting.public_primary_key, "value": "TEST_UPDATED_VALUE", "name": "TELEGRAM_WEBHOOK_HOST"} + + with mock.patch("telegram.Bot.set_webhook") as mock_set_webhook: + response = client.put(url, data=data, format="json", **make_user_auth_headers(user, token)) + + assert response.status_code == HTTP_200_OK + mock_set_webhook.assert_not_called() diff --git a/engine/apps/base/utils.py b/engine/apps/base/utils.py index 39eec6c5..ec15ac41 100644 --- a/engine/apps/base/utils.py +++ b/engine/apps/base/utils.py @@ -3,6 +3,7 @@ import re from urllib.parse import urlparse import phonenumbers +from django.conf import settings from phonenumbers import NumberParseException from telegram import Bot from twilio.base.exceptions import TwilioException @@ -137,6 +138,9 @@ class LiveSettingValidator: @classmethod def _check_telegram_webhook_host(cls, telegram_webhook_host): + if settings.FEATURE_TELEGRAM_LONG_POLLING_ENABLED: + return + try: # avoid circular import from apps.telegram.client import TelegramClient diff --git a/engine/apps/telegram/client.py b/engine/apps/telegram/client.py index 33e70072..8bd547b2 100644 --- a/engine/apps/telegram/client.py +++ b/engine/apps/telegram/client.py @@ -46,6 +46,13 @@ class TelegramClient: self.api_client.set_webhook(webhook_url, allowed_updates=self.ALLOWED_UPDATES) + def delete_webhook(self): + webhook_info = self.api_client.get_webhook_info() + if webhook_info.url == "": + return + + self.api_client.delete_webhook() + def send_message( self, chat_id: Union[int, str], diff --git a/engine/apps/telegram/tasks.py b/engine/apps/telegram/tasks.py index a650ad01..54b84945 100644 --- a/engine/apps/telegram/tasks.py +++ b/engine/apps/telegram/tasks.py @@ -28,6 +28,9 @@ logger.setLevel(logging.DEBUG) ) @handle_missing_token def register_telegram_webhook(token=None): + if settings.FEATURE_TELEGRAM_LONG_POLLING_ENABLED: + return + telegram_client = TelegramClient(token=token) try: diff --git a/engine/engine/celery.py b/engine/engine/celery.py index f0159207..49e1d0a3 100644 --- a/engine/engine/celery.py +++ b/engine/engine/celery.py @@ -57,7 +57,8 @@ def on_after_setup_logger(logger, **kwargs): def on_worker_ready(*args, **kwargs): from apps.telegram.tasks import register_telegram_webhook - register_telegram_webhook.delay() + if not settings.FEATURE_TELEGRAM_LONG_POLLING_ENABLED: + register_telegram_webhook.delay() if settings.OTEL_TRACING_ENABLED and settings.OTEL_EXPORTER_OTLP_ENDPOINT: diff --git a/engine/engine/management/commands/start_telegram_polling.py b/engine/engine/management/commands/start_telegram_polling.py new file mode 100644 index 00000000..4d15b434 --- /dev/null +++ b/engine/engine/management/commands/start_telegram_polling.py @@ -0,0 +1,50 @@ +import logging + +import telegram.error +from django.core.management.base import BaseCommand +from telegram.ext import CallbackQueryHandler, Filters, MessageHandler, Updater + +from apps.telegram.client import TelegramClient +from apps.telegram.updates.update_manager import UpdateManager + +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + + +def start_telegram_polling(): + telegram_client = TelegramClient() + + telegram_client.delete_webhook() + + updater = Updater(token=telegram_client.token, use_context=True) + + # Register the error handler function with the dispatcher + updater.dispatcher.add_error_handler(error_handler) + + callback_handler = CallbackQueryHandler(handle_message) + + # register the message handler function with the dispatcher + updater.dispatcher.add_handler(MessageHandler(Filters.text, handle_message)) + updater.dispatcher.add_handler(callback_handler) + + # start the long polling loop + updater.start_polling() + + +def error_handler(update, context): + try: + raise context.error + except telegram.error.Conflict as e: + logger.warning(f"Tried to getUpdates() using telegram long polling, but conflict exists, got error: {e}") + + +def handle_message(update, context): + logger.debug(f"Update from Telegram: {update}") + + UpdateManager.process_update(update) + + +class Command(BaseCommand): + def handle(self, *args, **options): + logger.info("Starting telegram polling...") + start_telegram_polling() diff --git a/engine/settings/base.py b/engine/settings/base.py index 0522be84..c2d4f13b 100644 --- a/engine/settings/base.py +++ b/engine/settings/base.py @@ -59,6 +59,7 @@ BASE_URL = os.environ.get("BASE_URL") # Root URL of OnCall backend # Feature toggles FEATURE_LIVE_SETTINGS_ENABLED = getenv_boolean("FEATURE_LIVE_SETTINGS_ENABLED", default=True) FEATURE_TELEGRAM_INTEGRATION_ENABLED = getenv_boolean("FEATURE_TELEGRAM_INTEGRATION_ENABLED", default=True) +FEATURE_TELEGRAM_LONG_POLLING_ENABLED = getenv_boolean("FEATURE_TELEGRAM_LONG_POLLING_ENABLED", default=False) FEATURE_EMAIL_INTEGRATION_ENABLED = getenv_boolean("FEATURE_EMAIL_INTEGRATION_ENABLED", default=True) FEATURE_SLACK_INTEGRATION_ENABLED = getenv_boolean("FEATURE_SLACK_INTEGRATION_ENABLED", default=True) FEATURE_MULTIREGION_ENABLED = getenv_boolean("FEATURE_MULTIREGION_ENABLED", default=False) diff --git a/helm/oncall/README.md b/helm/oncall/README.md index e55bc6fa..a4eaa782 100644 --- a/helm/oncall/README.md +++ b/helm/oncall/README.md @@ -214,7 +214,7 @@ oncall: `oncall.slack.commandName` is used for changing default bot slash command, `oncall`. In slack, it could be called via `/`. -To set up Telegram tokem and webhook url use: +To set up Telegram token and webhook url use: ```yaml oncall: @@ -224,6 +224,13 @@ oncall: webhookUrl: ~ ``` +To use Telegram long polling instead of webhook use: + +```yaml +telegramPolling: + enabled: true +``` + ### Set up external access Grafana OnCall can be connected to the external monitoring systems or grafana deployed to the other cluster. diff --git a/helm/oncall/templates/_env.tpl b/helm/oncall/templates/_env.tpl index 0999a13e..57b69500 100644 --- a/helm/oncall/templates/_env.tpl +++ b/helm/oncall/templates/_env.tpl @@ -95,9 +95,16 @@ {{- end }} {{- define "snippet.oncall.telegram.env" -}} +{{- if .Values.telegramPolling.enabled -}} +{{- $_ := set .Values.oncall.telegram "enabled" true -}} +{{- end -}} - name: FEATURE_TELEGRAM_INTEGRATION_ENABLED value: {{ .Values.oncall.telegram.enabled | toString | title | quote }} {{- if .Values.oncall.telegram.enabled }} +{{- if .Values.telegramPolling.enabled }} +- name: FEATURE_TELEGRAM_LONG_POLLING_ENABLED + value: {{ .Values.telegramPolling.enabled | toString | title | quote }} +{{- end }} - name: TELEGRAM_WEBHOOK_HOST value: {{ .Values.oncall.telegram.webhookUrl | default (printf "https://%s" .Values.base_url) | quote }} {{- if .Values.oncall.telegram.existingSecret }} diff --git a/helm/oncall/templates/telegram-polling/_helpers.tpl b/helm/oncall/templates/telegram-polling/_helpers.tpl new file mode 100644 index 00000000..d2053dc0 --- /dev/null +++ b/helm/oncall/templates/telegram-polling/_helpers.tpl @@ -0,0 +1,22 @@ +{{/* +Maximum of 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "oncall.telegramPolling.fullname" -}} +{{ include "oncall.fullname" . | trunc 45 }}-telegram-polling +{{- end }} + +{{/* +Telegram polling common labels +*/}} +{{- define "oncall.telegramPolling.labels" -}} +{{ include "oncall.labels" . }} +app.kubernetes.io/component: telegram-polling +{{- end }} + +{{/* +Telegram polling selector labels +*/}} +{{- define "oncall.telegramPolling.selectorLabels" -}} +{{ include "oncall.selectorLabels" . }} +app.kubernetes.io/component: telegram-polling +{{- end }} diff --git a/helm/oncall/templates/telegram-polling/deployment.yaml b/helm/oncall/templates/telegram-polling/deployment.yaml new file mode 100644 index 00000000..081599af --- /dev/null +++ b/helm/oncall/templates/telegram-polling/deployment.yaml @@ -0,0 +1,44 @@ +{{- if .Values.telegramPolling.enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "oncall.telegramPolling.fullname" . }} + labels: + {{- include "oncall.telegramPolling.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "oncall.telegramPolling.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "oncall.telegramPolling.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "oncall.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + initContainers: + {{- include "oncall.initContainer" . | nindent 8 }} + containers: + - name: telegram-polling + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: ['sh', '-c', 'python manage.py start_telegram_polling'] + env: + {{- include "snippet.oncall.env" . | nindent 12 }} + {{- include "snippet.oncall.telegram.env" . | nindent 12 }} + {{- include "snippet.db.env" . | nindent 12 }} + {{- include "snippet.broker.env" . | nindent 12 }} + {{- include "oncall.extraEnvs" . | nindent 12 }} + {{- with .Values.telegramPolling.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} +{{- end -}} diff --git a/helm/oncall/tests/__snapshot__/telegram_polling_deployment_test.yaml.snap b/helm/oncall/tests/__snapshot__/telegram_polling_deployment_test.yaml.snap new file mode 100644 index 00000000..18e4e440 --- /dev/null +++ b/helm/oncall/tests/__snapshot__/telegram_polling_deployment_test.yaml.snap @@ -0,0 +1,73 @@ +telegramPolling.enabled=true -> should create telegram polling deployment: + 1: | + - command: + - sh + - -c + - python manage.py start_telegram_polling + env: + - name: BASE_URL + value: https://example.com + - name: SECRET_KEY + valueFrom: + secretKeyRef: + key: SECRET_KEY + name: oncall + - name: MIRAGE_SECRET_KEY + valueFrom: + secretKeyRef: + key: MIRAGE_SECRET_KEY + name: oncall + - name: MIRAGE_CIPHER_IV + value: 1234567890abcdef + - name: DJANGO_SETTINGS_MODULE + value: settings.helm + - name: AMIXR_DJANGO_ADMIN_PATH + value: admin + - name: OSS + value: "True" + - name: UWSGI_LISTEN + value: "1024" + - name: BROKER_TYPE + value: rabbitmq + - name: GRAFANA_API_URL + value: http://oncall-grafana + - name: FEATURE_TELEGRAM_INTEGRATION_ENABLED + value: "True" + - name: FEATURE_TELEGRAM_LONG_POLLING_ENABLED + value: "True" + - name: TELEGRAM_WEBHOOK_HOST + value: https://example.com + - name: TELEGRAM_TOKEN + value: "" + - name: MYSQL_HOST + value: oncall-mariadb + - name: MYSQL_PORT + value: "3306" + - name: MYSQL_DB_NAME + value: oncall + - name: MYSQL_USER + value: root + - name: MYSQL_PASSWORD + valueFrom: + secretKeyRef: + key: mariadb-root-password + name: oncall-mariadb + - name: RABBITMQ_USERNAME + value: user + - name: RABBITMQ_PASSWORD + valueFrom: + secretKeyRef: + key: rabbitmq-password + name: oncall-rabbitmq + - name: RABBITMQ_HOST + value: oncall-rabbitmq + - name: RABBITMQ_PORT + value: "5672" + - name: RABBITMQ_PROTOCOL + value: amqp + - name: RABBITMQ_VHOST + value: "" + image: grafana/oncall:v1.3.20 + imagePullPolicy: Always + name: telegram-polling + securityContext: {} diff --git a/helm/oncall/tests/__snapshot__/wait_for_db_test.yaml.snap b/helm/oncall/tests/__snapshot__/wait_for_db_test.yaml.snap index 1b6ff0ef..8a4586aa 100644 --- a/helm/oncall/tests/__snapshot__/wait_for_db_test.yaml.snap +++ b/helm/oncall/tests/__snapshot__/wait_for_db_test.yaml.snap @@ -141,6 +141,77 @@ database.type=mysql -> should create initContainer for MySQL database (default): cpu: 100m memory: 128Mi securityContext: {} + 3: | + - command: + - sh + - -c + - until (python manage.py migrate --check); do echo Waiting for database migrations; sleep 2; done + env: + - name: BASE_URL + value: https://example.com + - name: SECRET_KEY + valueFrom: + secretKeyRef: + key: SECRET_KEY + name: oncall + - name: MIRAGE_SECRET_KEY + valueFrom: + secretKeyRef: + key: MIRAGE_SECRET_KEY + name: oncall + - name: MIRAGE_CIPHER_IV + value: 1234567890abcdef + - name: DJANGO_SETTINGS_MODULE + value: settings.helm + - name: AMIXR_DJANGO_ADMIN_PATH + value: admin + - name: OSS + value: "True" + - name: UWSGI_LISTEN + value: "1024" + - name: BROKER_TYPE + value: rabbitmq + - name: GRAFANA_API_URL + value: http://oncall-grafana + - name: MYSQL_HOST + value: oncall-mariadb + - name: MYSQL_PORT + value: "3306" + - name: MYSQL_DB_NAME + value: oncall + - name: MYSQL_USER + value: root + - name: MYSQL_PASSWORD + valueFrom: + secretKeyRef: + key: mariadb-root-password + name: oncall-mariadb + - name: RABBITMQ_USERNAME + value: user + - name: RABBITMQ_PASSWORD + valueFrom: + secretKeyRef: + key: rabbitmq-password + name: oncall-rabbitmq + - name: RABBITMQ_HOST + value: oncall-rabbitmq + - name: RABBITMQ_PORT + value: "5672" + - name: RABBITMQ_PROTOCOL + value: amqp + - name: RABBITMQ_VHOST + value: "" + image: grafana/oncall:v1.2.36 + imagePullPolicy: Always + name: wait-for-db + resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi + securityContext: {} database.type=postgresql -> should create initContainer for PostgreSQL database: 1: | - command: @@ -288,3 +359,76 @@ database.type=postgresql -> should create initContainer for PostgreSQL database: cpu: 100m memory: 128Mi securityContext: {} + 3: | + - command: + - sh + - -c + - until (python manage.py migrate --check); do echo Waiting for database migrations; sleep 2; done + env: + - name: BASE_URL + value: https://example.com + - name: SECRET_KEY + valueFrom: + secretKeyRef: + key: SECRET_KEY + name: oncall + - name: MIRAGE_SECRET_KEY + valueFrom: + secretKeyRef: + key: MIRAGE_SECRET_KEY + name: oncall + - name: MIRAGE_CIPHER_IV + value: 1234567890abcdef + - name: DJANGO_SETTINGS_MODULE + value: settings.helm + - name: AMIXR_DJANGO_ADMIN_PATH + value: admin + - name: OSS + value: "True" + - name: UWSGI_LISTEN + value: "1024" + - name: BROKER_TYPE + value: rabbitmq + - name: GRAFANA_API_URL + value: http://oncall-grafana + - name: DATABASE_TYPE + value: postgresql + - name: DATABASE_HOST + value: some-postgresql-host + - name: DATABASE_PORT + value: "5432" + - name: DATABASE_NAME + value: oncall + - name: DATABASE_USER + value: postgres + - name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + key: postgres-password + name: oncall-postgresql-external + - name: RABBITMQ_USERNAME + value: user + - name: RABBITMQ_PASSWORD + valueFrom: + secretKeyRef: + key: rabbitmq-password + name: oncall-rabbitmq + - name: RABBITMQ_HOST + value: oncall-rabbitmq + - name: RABBITMQ_PORT + value: "5672" + - name: RABBITMQ_PROTOCOL + value: amqp + - name: RABBITMQ_VHOST + value: "" + image: grafana/oncall:v1.2.36 + imagePullPolicy: Always + name: wait-for-db + resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi + securityContext: {} diff --git a/helm/oncall/tests/extra_env_test.yaml b/helm/oncall/tests/extra_env_test.yaml index 3ea4fa4e..8b502e27 100644 --- a/helm/oncall/tests/extra_env_test.yaml +++ b/helm/oncall/tests/extra_env_test.yaml @@ -3,11 +3,13 @@ templates: - engine/deployment.yaml - engine/job-migrate.yaml - celery/deployment.yaml + - telegram-polling/deployment.yaml release: name: oncall tests: - it: env=[] -> should support old syntax set: + telegramPolling.enabled: true env: - name: SOME_VAR value: some_value @@ -20,6 +22,7 @@ tests: - it: env=map[] -> should set multiple envs set: + telegramPolling.enabled: true env: SOME_VAR: some_value another_var: "another_value" @@ -39,7 +42,9 @@ tests: templates: - engine/deployment.yaml - celery/deployment.yaml + - telegram-polling/deployment.yaml set: + telegramPolling.enabled: true env: - name: SOME_VAR value: some_value @@ -54,7 +59,9 @@ tests: templates: - engine/deployment.yaml - celery/deployment.yaml + - telegram-polling/deployment.yaml set: + telegramPolling.enabled: true env: SOME_VAR: some_value another_var: "another_value" @@ -74,7 +81,9 @@ tests: templates: - engine/deployment.yaml - celery/deployment.yaml + - telegram-polling/deployment.yaml set: + telegramPolling.enabled: true database.type: postgresql postgresql.enabled: true env: @@ -96,7 +105,9 @@ tests: templates: - engine/deployment.yaml - celery/deployment.yaml + - telegram-polling/deployment.yaml set: + telegramPolling.enabled: true database.type: postgresql postgresql.enabled: true env: diff --git a/helm/oncall/tests/image_deployments_test.yaml b/helm/oncall/tests/image_deployments_test.yaml index d171aae7..c7a1ae09 100644 --- a/helm/oncall/tests/image_deployments_test.yaml +++ b/helm/oncall/tests/image_deployments_test.yaml @@ -3,12 +3,15 @@ templates: - celery/deployment.yaml - engine/deployment.yaml - engine/job-migrate.yaml + - telegram-polling/deployment.yaml release: name: oncall chart: appVersion: 1.2.36 tests: - it: image={} -> should use default image tag + set: + telegramPolling.enabled: true asserts: - equal: path: spec.template.spec.containers[0].image @@ -19,6 +22,7 @@ tests: - it: image.repository and image.tag -> should use custom image set: + telegramPolling.enabled: true image: repository: custom-oncall tag: 1.2.36-custom diff --git a/helm/oncall/tests/image_pull_secrets_test.yaml b/helm/oncall/tests/image_pull_secrets_test.yaml index 8cf22388..2bbe5b40 100644 --- a/helm/oncall/tests/image_pull_secrets_test.yaml +++ b/helm/oncall/tests/image_pull_secrets_test.yaml @@ -3,12 +3,14 @@ templates: - celery/deployment.yaml - engine/deployment.yaml - engine/job-migrate.yaml + - telegram-polling/deployment.yaml release: name: oncall tests: - it: imagePullSecrets=[] -> should not create spec.template.spec.imagePullSecrets set: imagePullSecrets: [] + telegramPolling.enabled: true asserts: - notExists: path: spec.template.spec.imagePullSecrets @@ -17,6 +19,7 @@ tests: set: imagePullSecrets: - name: regcred + telegramPolling.enabled: true asserts: - contains: path: spec.template.spec.imagePullSecrets diff --git a/helm/oncall/tests/mysql_env_test.yaml b/helm/oncall/tests/mysql_env_test.yaml index 845842bf..cc993045 100644 --- a/helm/oncall/tests/mysql_env_test.yaml +++ b/helm/oncall/tests/mysql_env_test.yaml @@ -3,11 +3,13 @@ templates: - engine/deployment.yaml - engine/job-migrate.yaml - celery/deployment.yaml + - telegram-polling/deployment.yaml release: name: oncall tests: - it: mariadb.enabled=false -> external MySQL default settings set: + telegramPolling.enabled: true mariadb.enabled: false asserts: - contains: @@ -38,6 +40,7 @@ tests: - it: externalMysql -> use external MySQL custom settings set: + telegramPolling.enabled: true mariadb.enabled: false externalMysql: host: test-host @@ -67,6 +70,8 @@ tests: value: test-host - it: mariadb.enabled=true -> internal MySQL default settings + set: + telegramPolling.enabled: true asserts: - contains: path: spec.template.spec.containers[0].env @@ -91,6 +96,7 @@ tests: - it: mariadb.auth -> internal MySQL custom settings set: + telegramPolling.enabled: true mariadb: auth: database: grafana_oncall diff --git a/helm/oncall/tests/mysql_password_env_test.yaml b/helm/oncall/tests/mysql_password_env_test.yaml index b57d815f..c6a6987e 100644 --- a/helm/oncall/tests/mysql_password_env_test.yaml +++ b/helm/oncall/tests/mysql_password_env_test.yaml @@ -5,6 +5,7 @@ templates: - engine/deployment.yaml - engine/job-migrate.yaml - celery/deployment.yaml + - telegram-polling/deployment.yaml - secrets.yaml tests: - it: secrets -> should fail if externalMysql.password not set @@ -20,7 +21,9 @@ tests: - engine/deployment.yaml - engine/job-migrate.yaml - celery/deployment.yaml + - telegram-polling/deployment.yaml set: + telegramPolling.enabled: true mariadb.enabled: false externalMysql: user: user123 diff --git a/helm/oncall/tests/postgres_env_test.yaml b/helm/oncall/tests/postgres_env_test.yaml index fccdb4f2..c46caaf9 100644 --- a/helm/oncall/tests/postgres_env_test.yaml +++ b/helm/oncall/tests/postgres_env_test.yaml @@ -3,11 +3,13 @@ templates: - engine/deployment.yaml - engine/job-migrate.yaml - celery/deployment.yaml + - telegram-polling/deployment.yaml release: name: oncall tests: - it: postgresql.enabled=false -> external PostgreSQL default settings set: + telegramPolling.enabled: true database.type: postgresql postgresql.enabled: false externalPostgresql.host: custom-postgres-host @@ -40,6 +42,7 @@ tests: - it: externalPostgresql -> should use external PostgreSQL custom settings set: + telegramPolling.enabled: true database.type: postgresql postgresql.enabled: false externalPostgresql: @@ -76,6 +79,7 @@ tests: - it: postgresql.enabled=true -> internal PostgreSQL default settings set: + telegramPolling.enabled: true database.type: postgresql postgresql.enabled: true asserts: @@ -107,6 +111,7 @@ tests: - it: postgresql.auth -> should use internal PostgreSQL custom settings set: + telegramPolling.enabled: true database.type: postgresql postgresql: enabled: true diff --git a/helm/oncall/tests/postgres_password_env_test.yaml b/helm/oncall/tests/postgres_password_env_test.yaml index e5b9f0f5..56ea50dc 100644 --- a/helm/oncall/tests/postgres_password_env_test.yaml +++ b/helm/oncall/tests/postgres_password_env_test.yaml @@ -5,6 +5,7 @@ templates: - engine/deployment.yaml - engine/job-migrate.yaml - celery/deployment.yaml + - telegram-polling/deployment.yaml - secrets.yaml tests: - it: secrets -> should fail if externalPostgresql.password not set @@ -23,7 +24,9 @@ tests: - engine/deployment.yaml - engine/job-migrate.yaml - celery/deployment.yaml + - telegram-polling/deployment.yaml set: + telegramPolling.enabled: true database.type: postgresql postgresql.enabled: false externalPostgresql: @@ -55,7 +58,9 @@ tests: - engine/deployment.yaml - engine/job-migrate.yaml - celery/deployment.yaml + - telegram-polling/deployment.yaml set: + telegramPolling.enabled: true database.type: postgresql postgresql.enabled: false externalPostgresql: @@ -77,7 +82,9 @@ tests: - engine/deployment.yaml - engine/job-migrate.yaml - celery/deployment.yaml + - telegram-polling/deployment.yaml set: + telegramPolling.enabled: true database.type: postgresql postgresql.enabled: false externalPostgresql: @@ -99,7 +106,9 @@ tests: - engine/deployment.yaml - engine/job-migrate.yaml - celery/deployment.yaml + - telegram-polling/deployment.yaml set: + telegramPolling.enabled: true database.type: postgresql postgresql: enabled: true diff --git a/helm/oncall/tests/security_context_deployments_test.yaml b/helm/oncall/tests/security_context_deployments_test.yaml index 8f0f4055..5514c24c 100644 --- a/helm/oncall/tests/security_context_deployments_test.yaml +++ b/helm/oncall/tests/security_context_deployments_test.yaml @@ -3,11 +3,13 @@ templates: - celery/deployment.yaml - engine/deployment.yaml - engine/job-migrate.yaml + - telegram-polling/deployment.yaml release: name: oncall tests: - it: podSecurityContext={} -> spec.template.spec.securityContext is empty (default) set: + telegramPolling.enabled: true asserts: - isNullOrEmpty: path: spec.template.spec.securityContext @@ -16,6 +18,7 @@ tests: - it: podSecurityContext.runAsNonRoot=true -> should fill securityContext set: + telegramPolling.enabled: true podSecurityContext: runAsNonRoot: true runAsUser: 1000 @@ -28,6 +31,7 @@ tests: - it: securityContext.runAsNonRoot=true -> should fill securityContext for container set: + telegramPolling.enabled: true securityContext: runAsNonRoot: true runAsUser: 1000 diff --git a/helm/oncall/tests/service_account_deployments_test.yaml b/helm/oncall/tests/service_account_deployments_test.yaml index 0c1d7358..89bf134d 100644 --- a/helm/oncall/tests/service_account_deployments_test.yaml +++ b/helm/oncall/tests/service_account_deployments_test.yaml @@ -3,10 +3,13 @@ templates: - celery/deployment.yaml - engine/deployment.yaml - engine/job-migrate.yaml + - telegram-polling/deployment.yaml release: name: oncall tests: - it: serviceAccount.create=true -> should use created serviceAccount for deployments (default) + set: + telegramPolling.enabled: true asserts: - equal: path: spec.template.spec.serviceAccountName @@ -14,6 +17,7 @@ tests: - it: serviceAccount.create=false -> should use default serviceAccount for deployments set: + telegramPolling.enabled: true serviceAccount.create: false asserts: - equal: @@ -22,6 +26,7 @@ tests: - it: serviceAccount.name=custom -> should use created custom serviceAccount for deployments set: + telegramPolling.enabled: true serviceAccount.name: custom asserts: - equal: diff --git a/helm/oncall/tests/telegram_env_test.yaml b/helm/oncall/tests/telegram_env_test.yaml index 65e94cd3..288d5ee6 100644 --- a/helm/oncall/tests/telegram_env_test.yaml +++ b/helm/oncall/tests/telegram_env_test.yaml @@ -2,10 +2,14 @@ suite: test telegram envs for deployments templates: - engine/deployment.yaml - celery/deployment.yaml + - telegram-polling/deployment.yaml release: name: oncall tests: - it: oncall.telegram.enabled=false -> Telegram integration disabled (default) + templates: + - engine/deployment.yaml + - celery/deployment.yaml asserts: - contains: path: spec.template.spec.containers[0].env @@ -14,6 +18,9 @@ tests: value: "False" - it: oncall.telegram.enabled=true -> should enable Telegram integration + templates: + - engine/deployment.yaml + - celery/deployment.yaml set: oncall.telegram: enabled: true @@ -37,6 +44,9 @@ tests: value: "abcd:123" - it: oncall.telegram.existingSecret=some-secret -> should prefer existing secret over oncall.telegram.token + templates: + - engine/deployment.yaml + - celery/deployment.yaml set: oncall.telegram: enabled: true @@ -52,3 +62,18 @@ tests: secretKeyRef: name: some-secret key: token + + - it: telegramPolling.enabled=true -> should enable oncall.telegram.enabled too + set: + telegramPolling.enabled: true + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: FEATURE_TELEGRAM_INTEGRATION_ENABLED + value: "True" + - contains: + path: spec.template.spec.containers[0].env + content: + name: FEATURE_TELEGRAM_LONG_POLLING_ENABLED + value: "True" diff --git a/helm/oncall/tests/telegram_polling_deployment_test.yaml b/helm/oncall/tests/telegram_polling_deployment_test.yaml new file mode 100644 index 00000000..96fb8d01 --- /dev/null +++ b/helm/oncall/tests/telegram_polling_deployment_test.yaml @@ -0,0 +1,73 @@ +suite: test telegram polling deployment +templates: + - telegram-polling/deployment.yaml +release: + name: oncall +tests: + - it: telegramPolling.enabled=false -> should not create deployment (default) + asserts: + - hasDocuments: + count: 0 + + - it: telegramPolling.enabled=true -> should create telegram polling deployment + set: + telegramPolling.enabled: true + asserts: + - containsDocument: + kind: Deployment + apiVersion: apps/v1 + name: oncall-telegram-polling + - isSubset: + path: metadata.labels + content: + app.kubernetes.io/component: telegram-polling + app.kubernetes.io/instance: oncall + app.kubernetes.io/name: oncall + - isSubset: + path: spec.selector.matchLabels + content: + app.kubernetes.io/component: telegram-polling + app.kubernetes.io/instance: oncall + app.kubernetes.io/name: oncall + - isSubset: + path: spec.template.metadata.labels + content: + app.kubernetes.io/component: telegram-polling + app.kubernetes.io/instance: oncall + app.kubernetes.io/name: oncall + # Should contain only one replica to avoid Conflict while polling Telegram updates + - equal: + path: spec.replicas + value: 1 + - equal: + path: spec.template.spec.serviceAccountName + value: oncall + - contains: + path: spec.template.spec.initContainers + content: + name: wait-for-db + any: true + - matchSnapshot: + path: spec.template.spec.containers + + - it: telegramPolling.resources -> should specify resources + set: + telegramPolling: + enabled: true + resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi + asserts: + - equal: + path: spec.template.spec.containers[0].resources + value: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi diff --git a/helm/oncall/tests/wait_for_db_test.yaml b/helm/oncall/tests/wait_for_db_test.yaml index 6860389a..b1fed706 100644 --- a/helm/oncall/tests/wait_for_db_test.yaml +++ b/helm/oncall/tests/wait_for_db_test.yaml @@ -2,6 +2,7 @@ suite: test init container wait-for-db in deployments templates: - celery/deployment.yaml - engine/deployment.yaml + - telegram-polling/deployment.yaml release: name: oncall chart: @@ -16,6 +17,8 @@ tests: requests: cpu: 100m memory: 128Mi + telegramPolling.enabled: true + database.type: mysql asserts: - contains: path: spec.template.spec.initContainers @@ -32,6 +35,7 @@ tests: - it: database.type=postgresql -> should create initContainer for PostgreSQL database set: + telegramPolling.enabled: true database.type: postgresql externalPostgresql.host: some-postgresql-host init.resources: diff --git a/helm/oncall/values.yaml b/helm/oncall/values.yaml index d6f71ac7..73f07b4d 100644 --- a/helm/oncall/values.yaml +++ b/helm/oncall/values.yaml @@ -134,6 +134,17 @@ celery: # - --port=5432 # - example:europe-west3:grafana-oncall-db +# Telegram polling pod configuration +telegramPolling: + enabled: false + resources: {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + oncall: # this is intended to be used for local development. In short, it will mount the ./engine dir into # any backend related containers, to allow hot-reloading + also run the containers with slightly modified