Оглавление
- вступление
- Настраивать
- Webhook
- Подписка
- Полезная нагрузка
- Webhook Revisited
- Сообщения по маршрутизации и стилии
- Уклоняясь
- Авторизация
- Обработайте событие только один раз
- Обработка отказов
- Заворачивать
вступление
Привет! В эпизоде Спросите осьминога Я говорил о том, как использование осьминога Подписки И пользовательские веб -крючки могут решить много проблем. Я представил использование случая отправки слабой уведомления для каждого производственного развертывания. Я хотел изучить эту идею более подробно, что заставило меня написать этот пост. Итак, давайте доберемся до этого и настроем Slack уведомления для всех наших развертываний производства.
Настраивать
У меня есть шесть существующих проектов без каких -либо уведомлений, встроенных в их процессы. Количество проектов здесь не является необходимым. Тот же подход будет работать, независимо от того, есть ли у вас один проект или сотни.
Webhook
Я собираюсь начать с настройки WebHook по двум причинам. Первая причина заключается в том, что мне нужен URL -адрес Webhook для настройки подписки. Во -вторых, я хочу использовать WebHook, чтобы осмотреть полезную нагрузку, чтобы я знал, какие данные ожидать. Я начну с простой функции, которая принимает веб -запрос и регистрирует тело этого запроса. Я использую функции Firebase Cloud в качестве своей конечной точки для этой демонстрации.
exports.logOctopusEvent = functions.https.onRequest((req, res) => { console.log(JSON.stringify(req.body)); return res.status(200).end(); });
Подписка
У нас есть элементарный веб -крючок, поэтому давайте настроим эту подписку и запустим развертывание.
Я выбрал три категории событий, которые должны быть отправлены в WebHook для обработки: развертывание началось, развертывание удалось и развертывание не удалось. Я также установил фильтр среды только для производства. Когда вы впервые настраиваете веб -крюк, вы можете сначала ограничить его в средах разработки или тестирования, когда вы создаете свою логику.
Я также установил URL -адрес полезной нагрузки на URL моего веб -кхука.
Полезная нагрузка
После запуска развертывания я вытащил тело полезной нагрузки из журналов функций. Здесь много информации, включая детали подписки, которая вызвала запрос.
Информация, которая нас больше всего интересует, находится в полезной нагрузке. Раздел события. В частности, мы собираемся использовать категорию, сообщение и связанные с ними.
{ "Timestamp": "2019-04-26T18:37:44.1581725+00:00", "EventType": "SubscriptionPayload", "Payload": { "ServerUri": "https://myoctopusurl", "ServerAuditUri": "https://myoctopusurl/#/configuration/audit?environments=Environments-246&eventCategories=DeploymentFailed&eventCategories=DeploymentStarted&eventCategories=DeploymentSucceeded&from=2019-04-26T18%3a37%3a12.%2b00%3a00&to=2019-04-26T18%3a37%3a42.%2b00%3a00", "BatchProcessingDate": "2019-04-26T18:37:42.7832114+00:00", "Subscription": { "Id": "Subscriptions-161", "Name": "Production Deployments", "Type": 0, "IsDisabled": false, "EventNotificationSubscription": { "Filter": { "Users": [], "Projects": [], "Environments": [ "Environments-246" ], "EventGroups": [], "EventCategories": [ "DeploymentFailed", "DeploymentStarted", "DeploymentSucceeded" ], "EventAgents": [], "Tenants": [], "Tags": [], "DocumentTypes": [] }, "EmailTeams": [], "EmailFrequencyPeriod": "01:00:00", "EmailPriority": 0, "EmailDigestLastProcessed": null, "EmailDigestLastProcessedEventAutoId": null, "EmailShowDatesInTimeZoneId": "UTC", "WebhookURI": "https://mywebhookurl/logOctopusEvent", "WebhookTeams": [], "WebhookTimeout": "00:00:10", "WebhookHeaderKey": null, "WebhookHeaderValue": null, "WebhookLastProcessed": "2019-04-26T18:37:12.4560433+00:00", "WebhookLastProcessedEventAutoId": 187275 }, "SpaceId": "Spaces-83", "Links": { "Self": {} } }, "Event": { "Id": "Events-189579", "RelatedDocumentIds": [ "Deployments-15970", "Projects-670", "Releases-6856", "Environments-246", "ServerTasks-318123", "Channels-690", "ProjectGroups-302" ], "Category": "DeploymentStarted", "UserId": "users-system", "Username": "system", "IsService": false, "IdentityEstablishedWith": "", "UserAgent": "Server", "Occurred": "2019-04-26T18:37:34.3616214+00:00", "Message": "Deploy to Prod (#3) started for Accounting Database release 10.33.210 to Prod", "MessageHtml": "Deploy to Prod (#3) started for Accounting Database release 10.33.210 to Prod", "MessageReferences": [ { "ReferencedDocumentId": "Deployments-15970", "StartIndex": 0, "Length": 19 }, { "ReferencedDocumentId": "Projects-670", "StartIndex": 33, "Length": 19 }, { "ReferencedDocumentId": "Releases-6856", "StartIndex": 61, "Length": 9 }, { "ReferencedDocumentId": "Environments-246", "StartIndex": 74, "Length": 4 } ], "Comments": null, "Details": null, "SpaceId": "Spaces-83", "Links": { "Self": {} } }, "BatchId": "e6df5aae-a42a-4bd8-8b0d-43065f82d5f0", "TotalEventsInBatch": 1, "EventNumberInBatch": 1 } }
Webhook Revisited
Первоначальный веб -крюк настроен. Подписка отправляет события на это. Давайте добавим некоторую реальную логику в функцию.
Сначала мы проверяем, есть ли у нас полезная нагрузка. Если у нас его нет, мы отправим ответ плохого запроса.
Затем мы извлекаем имя подписки и сообщение и используем его для создания слабых сообщений.
exports.logOctopusEvent = functions.https.onRequest((req, res) => { const payload = req.body.Payload; if (payload) { return sendSlackMessage({ "text": payload.Event.Message, "username": `Octopus Subscription: ${payload.Subscription.Name}` }).then(() => { return res.status(200).send(); }); } else { console.warn('No payload provided'); return res.status(400).send('No payload provided'); } });
После продвижения этого изменения и вызвать другое развертывание, мы получаем некоторые уведомления на нашем канале!
Сообщения по маршрутизации и стилии
Наш Slack Webhook отправляет сообщения на канал #Octopus-развертывания по умолчанию. Однако что, если мы хотим отправить уведомления на другой канал на основе проекта?
Мы можем вытащить идентификатор проекта из соответствующих идентификаторов документа.
const projectId = payload.Event.RelatedDocumentIds.find(id => id.startsWith('Projects-'));
Мы можем создать отображение от идентификатора проекта на канал, который следует использовать.
const projectToChannel = { "Projects-670": "#accounting_dev", "Projects-665": "#accounting_dev", "Projects-668": "#expense_dev", "Projects-667": "#expense_dev", "Projects-669": "#hr_dev", "Projects-666": "#hr_dev" };
А затем предоставьте этот канал нашей функции Slack.
return sendSlackMessage({ "channel": projectToChannel[projectId], "text": payload.Event.Message, "username": `Octopus Subscription: ${payload.Subscription.Name}` }).then(() => { return res.status(200).send(); });
Большой! Теперь давайте добавим немного таланта в наши сообщения, используя категорию. Точно так же мы настроим отображение канала, мы можем настроить отображение из категории на смайлику.
const categoryToEmoji = { "DeploymentStarted": ":octopusdeploy:", "DeploymentFailed": ":fire:", "DeploymentSucceeded": ":tada:" }
А затем используйте это отображение, чтобы выбрать смайлики и добавить его в наше сообщение.
const projectId = payload.Event.RelatedDocumentIds.find(id => id.startsWith('Projects-')); const channel = projectToChannel[projectId]; const emoji = categoryToEmoji[payload.Event.Category]; return sendSlackMessage({ "channel": channel, "text": `${emoji} ${payload.Event.Message} ${emoji}`, "username": `Octopus Subscription: ${payload.Subscription.Name}` }).then(() => { return res.status(200).send(); });
Возможно, вы даже захотите взять категорию развертывания и отправить несколько прямых сообщений пользователю или отправить текстовое сообщение или электронное письмо. Мы оставим этот эксперимент на другой день.
Уклоняясь
Я храню свои отображения в базе данных и получаю их при выполнении функции. Код начинает становиться довольно занятым, поэтому давайте начнем разделить эти функции и звонить в трубопровод.
function getPayload([req, res]) { const payload = req.body.Payload; if (payload) { return Promise.resolve(payload); } return Promise.reject({ code: 400, message: 'No payload provided' }); } function loadMappings(payload) { if (categoryToEmojiMapping && projectToChannelMapping) { return Promise.resolve([payload, categoryToEmojiMapping, projectToChannelMapping]); } const collection = db.collection("mappings"); const categoryToEmojiPromise = collection.doc('categoryToEmoji').get(); const projectToChannelPromise = collection.doc('projectToChannel').get(); return Promise.all([categoryToEmojiPromise, projectToChannelPromise]) .then(([categoryToEmojiDoc, projectToChannelDoc]) => { categoryToEmojiMapping = categoryToEmojiDoc.data(); projectToChannelMapping = projectToChannelDoc.data(); return [payload, categoryToEmojiMapping, projectToChannelMapping]; }); } function createSlackOptions([payload, categoryToEmoji, projectToChannel]) { const projectId = payload.Event.RelatedDocumentIds.find(id => id.startsWith('Projects-')); const channel = projectToChannel[projectId]; const emoji = categoryToEmoji[payload.Event.Category]; return { "channel": channel, "text": `${emoji} ${payload.Event.Message} ${emoji}`, "username": `Octopus Subscription: ${payload.Subscription.Name}` }; } function sendSlackMessage(options) { const slackUri = functions.config().slack.uri; const requestOptions = { method: 'POST', uri: slackUri, body: { "channel": options.channel, "username": options.username, "icon_emoji": ":octopusdeploy:", "text": options.text }, json: true } return rp(requestOptions); } exports.logOctopusEvent = functions.https.onRequest((req, res) => { return getPayload([req, res]) .then(loadMappings) .then(createSlackOptions) .then(sendSlackMessage) .then(() => { return res.status(200).send(); }); });
Идеальный! Этот код начинает выглядеть намного лучше.
Авторизация
Мы еще не обратились к авторизации для нашего веб -крючка. В соответствии с этим, любой может отправить запрос, который соответствует структуре, которую мы ожидаем. Это потому, что я использую общую функцию облака Firebase. Если вы используете внутреннюю сервис или что -то размещенное, но с заблокированным доступом, вы можете не беспокоиться об этой части.
Мы можем исправить это, добавив заголовок к нашему запросу, который содержит токен авторизации, которому мы доверяем на стороне веб -крючка.
Я назвал свой заголовок Octolog-Token, но вы можете назвать это все, что вам нравится. Вы даже можете использовать стандартное название заголовка, такое как авторизация. Здесь также важно отметить, что значение заголовка хранится в простом тексте. Вы захотите помнить об этом при принятии решения о том, какие значения заголовка использовать и какие команды предоставить доступ к подписке.
Давайте добавим функцию для обработки авторизации и вставьте ее в наш трубопровод.
function authorizeRequest(req, res) { const providedToken = req.get('octolog-token'); const token = functions.config().octolog.authtoken; if (!providedToken || providedToken !== token) { return Promise.reject({ code: 401, message: 'Missing or invalid token' }); } return Promise.resolve([req, res]); } exports.logOctopusEvent = functions.https.onRequest((req, res) => { return authorizeRequest(req, res) .then(getPayload) .then(loadMappings) .then(createSlackOptions) .then(sendSlackMessage) .then(() => { return res.status(200).send(); }); });
Теперь запросы без заголовка или с неверным токеном будут отклонены.
Обработайте событие только один раз
Я хочу назвать этот намек из нашей документации по подписке.
Несмотря на то, что мы прилагаем все усилия, чтобы события были отправлены только один раз в данную электронную почту или подписку на Webhook, мы не можем предложить никаких гарантий и посоветовать, что вы разработаете свой потребляющий API с учетом этого.
В этом случае дублирующее уведомление о слабых в худшем случае сбивает с толку. Это не повод игнорировать руководство в докоре! Давайте добавим логику, чтобы убедиться, что мы обрабатываем каждое событие только один раз. База данных Firestore поддерживает транзакции, поэтому мы используем их, чтобы убедиться, что мы не обрабатываем какие -либо события, уже в нашей базе данных.
function checkForDuplicate(payload) { return db.runTransaction((transaction) => { const eventReference = db.collection("deployments").doc(payload.Event.Id); return transaction.get(eventReference).then((eventDoc) => { if (eventDoc.exists) { return Promise.reject({ code: 200, message: `Event ${payload.Event.Id} has already been processed.` }); } transaction.set(eventReference, payload); console.log("Document written with ID: ", payload.Event.Id); return payload; }); }); } exports.logOctopusEvent = functions.https.onRequest((req, res) => { return authorizeRequest(req, res) .then(getPayload) .then(checkForDuplicate) .then(loadMappings) .then(createSlackOptions) .then(sendSlackMessage) .then(() => { return res.status(200).send(); }); });
Обработка отказов
Возможно, вы заметили некоторые призывы обещать. Derject, посыпанный по всему функциям. Возможно, вы также заметили, что мы нигде не справляемся с этими отказами.
В наших отказах мы отправляем объект с кодом и сообщением. Мы можем проверить этот формат, когда мы обрабатываем отказ. Если он соответствует, мы будем использовать этот код и сообщение. Если нет, мы отправим обратно общий ответ плохого запроса.
function handleRejection(res, reason) { if (reason.message) { console.warn(reason.message); return res.status(reason.code).send(reason.message); } console.warn(reason); return res.status(400).send(); } exports.logOctopusEvent = functions.https.onRequest((req, res) => { const sendOkResponse = () => { return res.status(200).send(); }; const callHandleRejection = (reason) => { return handleRejection(res, reason); } return authorizeRequest(req, res) .then(getPayload) .then(checkForDuplicate) .then(loadMappings) .then(createSlackOptions) .then(sendSlackMessage) .then(sendOkResponse) .catch(callHandleRejection); });
Заворачивать
Вот и все! Мы начали с ничего и создали функцию WebHook, которая не только отправляет слабые уведомления для всех развертываний производства, но и направляет их на соответствующий канал.
Я надеюсь, что это было полезным для вас прохождением. Я сохранил образец полезной нагрузки и функционального файла AT GitHub Анкет
Пожалуйста, оставьте любые отзывы или задайте любые вопросы ниже в комментариях. Если у вас был успех с подписками и веб -крючками, поделитесь с нами своей историей!
Этот пост был первоначально опубликован в octopus.com .
Оригинал: «https://dev.to/octopus/adding-notifications-for-every-production-deployment-in-octopus-deploy-g5i»