В прошлой статье мы разобрали пуши в Harbor. Сегодня же я расскажу о других методах, с которыми работает его webhook. Кстати, все эти методы приведены на изображении ниже:

Перейдем к разбору наиболее популярных событий.
Основные события webhook
SCANNING_STOPPED Это событие срабатывает, если вручную остановить сканирование образа.
PULL_ARTIFACT Происходит, когда кто-то скачивает ( pulls ) образ.
SCANNING_COMPLETED Сообщает о завершении сканирования образа и содержит краткую информацию о результатах.
DELETE_ARTIFACT Срабатывает при удалении образа.
Настройка конфигурации
Для удобства мы вынесем некоторые параметры в отдельный конфиг, например, список символов для экранирования и типы уведомлений, чтобы не сбивать лишней информацией там, где она не нужна.
# conf.toml [telegram] bot_token = "YOUR_BOT_TOKEN" chat_id = "-1001234567890" message_thread_id = "1234" # или оставьте пустым/удалите, если не нужно [markdown] # Список символов, которые будем экранировать escape_chars = ["\\", "_", "*", "[", "]", "(", ")", "~", "`", ">", "#", "+", "-", "=", "|", "{", "}", "!"] [events] # Включаем/выключаем оповещения по типам событий PUSH_ARTIFACT = true PULL_ARTIFACT = true DELETE_ARTIFACT = true SCANNING_STOPPED = true SCANNING_COMPLETED = true
Разбор каждого события
Для наглядности я подготовил общий JSON с описанием событий:
[ { "type": "SCANNING_STOPPED", "occur_at": 1746597835, "operator": "auto", "event_data": { "resources": [ { "digest": "sha256:280b85260a6613bf08bd1234512341234123412341234123412gvsdfgdf", "tag": "1.1.1", "resource_url": "127.0.0.1/repo/repo_image:1.1.1", "scan_overview": { "application/vnd.security.vulnerability.report; version=1.1": { "report_id": "ec84315d-3f08-42cc-bdc7-491953540cda", "scan_status": "Stopped", "severity": "", "duration": 13, "summary": null, "start_time": "2025-05-07T06:03:42Z", "end_time": "2025-05-07T06:03:55Z", "complete_percent": 0 } } } ], "repository": { "name": "repo_image", "namespace": "repo", "repo_full_name": "repo/repo_image", "repo_type": "public" } } }, { "type": "PULL_ARTIFACT", "occur_at": 1746597854, "operator": "robot$repo+Trivy-16c5e31b-2b09-11f0-87be-0242ac180003", "event_data": { "resources": [ { "digest": "sha256:280b85260a6613bf08bd1234512341234123412341234123412gvsdfgdf", "tag": "sha256:280b85260a6613bf08bd1234512341234123412341234123412gvsdfgdf", "resource_url": "127.0.0.1/repo/repo_image:@sha256:280b85260a6613bf08bd1234512341234123412341234123412gvsdfgdf" } ], "repository": { "date_created": 1733465413, "name": "repo_image", "namespace": "repo", "repo_full_name": "repo/repo_image", "repo_type": "public" } } }, { "type": "SCANNING_COMPLETED", "occur_at": 1746597860, "operator": "auto", "event_data": { "resources": [ { "digest": "sha256:280b85260a6613bf08bd1234512341234123412341234123412gvsdfgdf", "tag": "1.1.1", "resource_url": "127.0.0.1/repo/repo_image:1.1.1", "scan_overview": { "application/vnd.security.vulnerability.report; version=1.1": { "report_id": "39ba459d-3642-4de6-aaac-865b94cdb006", "scan_status": "Success", "severity": "Critical", "duration": 12, "summary": { "total": 1005, "fixable": 7, "summary": { "Critical": 2, "High": 73, "Low": 422, "Medium": 506, "Unknown": 2 } }, "start_time": "2025-05-07T06:04:08Z", "end_time": "2025-05-07T06:04:20Z", "scanner": { "name": "Trivy", "vendor": "Aqua Security", "version": "v0.45.0" }, "complete_percent": 100 } } } ], "repository": { "name": "repo_image", "namespace": "repo", "repo_full_name": "repo/repo_image", "repo_type": "public" } } }, { "type": "DELETE_ARTIFACT", "occur_at": 1746597914, "operator": "Admin", "event_data": { "resources": [ { "digest": "sha256:280b85260a6613bf08bd1234512341234123412341234123412gvsdfgdf123123123123123123", "tag": "1.0.0", "resource_url": "127.0.0.1/repo/repo_image:1.0.0" } ], "repository": { "date_created": 1733465413, "name": "repo_image", "namespace": "repo", "repo_full_name": "repo/repo_image", "repo_type": "public" } } } ]
PULL_ARTIFACT
PULL_ARTIFACT - это оповещение, которое можно использовать для отслеживания, когда кто-то скачивает образ. Важное замечание — чаще всего это инициирует автоматическое сканирование или другие процессы. В моем случае я решил оставить его как информативное событие, чтобы знать, когда образ попал в использование.
elif event_type == "PULL_ARTIFACT": for r in resources: tag = escape_markdown(r.get("tag", "—")) url = r.get("resource_url", "—") msg = ( "📥 *Pull в Harbor*\n" f"*Репозиторий:* {repo_name}\n" f"*Тег:* {tag}\n" f"*URL:* `{url}`\n" f"*Пользователь:* {operator}" ) messages.append(msg)
DELETE_ARTIFACT
DELETE_ARTIFACT - это событие помогает отслеживать, кто и когда удаляет образы, особенно актуально, если у вас несколько Harbor-репозиториев. Вот пример сообщения:
elif event_type == "DELETE_ARTIFACT": for r in resources: tag = escape_markdown(r.get("tag", "—")) url = r.get("resource_url", "—") msg = ( "🗑️ *Удаление образа в Harbor*\n" f"*Репозиторий:* {repo_name}\n" f"*Тег:* {tag}\n" f"*URL:* `{url}`\n" f"*Пользователь:* {operator}" ) messages.append(msg)
Хотя смайлики — не обязательная часть сообщений, я считаю, что они привлекают больше внимания и делают оповещения более дружелюбными.
SCANNING_STOPPED
SCANNING_STOPPED - это относительно редкое событие, срабатывающее при ручной остановке процесса сканирования. В моем опыте — очень редко, но бывает полезным для ситуаций, когда нужно срочно прервать проверку образа.
elif event_type == "SCANNING_STOPPED": for r in resources: tag = escape_markdown(r.get("tag", "—")) url = r.get("resource_url", "—") overview = r.get("scan_overview", {}) detail = next(iter(overview.values()), {}) status = escape_markdown(detail.get("scan_status", "—")) duration = detail.get("duration", 0) msg = ( "🔍 *Сканирование остановлено*\n" f"*Репозиторий:* {repo_name}\n" f"*Тег:* {tag}\n" f"*URL:* `{url}`\n" f"*Статус скана:* {status}\n" f"*Продолжительность:* {duration}s\n" f"*Пользователь:* {operator}" ) messages.append(msg)
SCANNING_COMPLETED
SCANNING_COMPLETED - на мой взгляд, одно из самых важных событий. После завершения сканирования Harbor сообщает о найденных уязвимостях и предлагает варианты устранения (например, обновление пакетов). Это очень удобно для быстрого анализа состояния образа и сравнения с предыдущими версиями.
elif event_type == "SCANNING_COMPLETED": for r in resources: tag = escape_markdown(r.get("tag", "—")) url = r.get("resource_url", "—") overview = r.get("scan_overview", {}) detail = next(iter(overview.values()), {}) status = escape_markdown(detail.get("scan_status", "—")) duration = detail.get("duration", 0) severity = escape_markdown(detail.get("severity", "—")) summary = detail.get("summary", {}) or {} total = summary.get("total", 0) fixable = summary.get("fixable", 0) counts = summary.get("summary", {}) # Формируем разбивку по уровням критичности counts_lines = "" for lvl, cnt in counts.items(): lvl_esc = escape_markdown(lvl) counts_lines += f"*{lvl_esc}:* {cnt}\n" scanner = detail.get("scanner", {}) sc_name = escape_markdown(scanner.get("name", "—")) sc_ver = escape_markdown(scanner.get("version", "—")) msg = ( "🔍 *Сканирование завершено*\n" f"*Репозиторий:* {repo_name}\n" f"*Тег:* {tag}\n" f"*URL:* `{url}`\n" f"*Статус скана:* {status}\n" f"*Продолжительность:* {duration}s\n" f"*Критичность (макс):* {severity}\n" f"*Всего уязвимостей:* {total}\n" f"*Исправимо:* {fixable}\n" f"{counts_lines}" f"*Сканер:* {sc_name} {sc_ver}\n" f"*Пользователь:* {operator}" ) messages.append(msg)
Это уведомление позволяет быстро оценить безопасность образа и принять меры.
Заключение и планы
На данный момент я собираю JSON-описания по другим событиям webhook. В дальнейшем хочу реализовать возможность редактирования настроек контейнера прямо через веб-интерфейс — на мой взгляд, это значительно упрощает работу. Надеюсь, статья оказалась полезной. Следите за обновлениями! Если кому необходимо, вот репозиторий с проектом.
