mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-18 04:38:09 +00:00
Add EventGrid compatible webhook format (#1640)
This commit is contained in:
@ -21,6 +21,27 @@ Each event will be submitted via HTTP POST to the user provided URL.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Event Grid Payload format
|
||||||
|
|
||||||
|
If webhook is set to have Event Grid message format then the payload will look as follows:
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"ping_id": "00000000-0000-0000-0000-000000000000"
|
||||||
|
},
|
||||||
|
"dataVersion": "1.0.0",
|
||||||
|
"eventTime": "0001-01-01T00:00:00",
|
||||||
|
"eventType": "ping",
|
||||||
|
"id": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"subject": "example"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
## Event Types (EventType)
|
## Event Types (EventType)
|
||||||
|
|
||||||
* [crash_reported](#crash_reported)
|
* [crash_reported](#crash_reported)
|
||||||
|
@ -26,6 +26,21 @@ $ onefuzz webhooks create MYWEBHOOK https://contoso.com/my-custom-webhook task_c
|
|||||||
$
|
$
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Example creating a webhook subscription only the `task_created` events that produces webhook data in [Azure Event Grid](https://docs.microsoft.com/en-us/azure/event-grid/event-schema) compatible format:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ onefuzz webhooks create MYWEBHOOK https://contoso.com/my-custom-webhook task_created --message_format event_grid
|
||||||
|
{
|
||||||
|
"webhook_id": "cc6926de-7c6f-487e-96ec-7b632d3ed52b",
|
||||||
|
"name": "MYWEBHOOK",
|
||||||
|
"event_types": [
|
||||||
|
"task_created"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
$
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Listing existing webhooks
|
### Listing existing webhooks
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import hmac
|
import hmac
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
from hashlib import sha512
|
from hashlib import sha512
|
||||||
from typing import List, Optional, Tuple
|
from typing import List, Optional, Tuple
|
||||||
@ -16,7 +17,11 @@ from onefuzztypes.enums import ErrorCode, WebhookMessageState
|
|||||||
from onefuzztypes.events import Event, EventMessage, EventPing, EventType
|
from onefuzztypes.events import Event, EventMessage, EventPing, EventType
|
||||||
from onefuzztypes.models import Error, Result
|
from onefuzztypes.models import Error, Result
|
||||||
from onefuzztypes.webhooks import Webhook as BASE_WEBHOOK
|
from onefuzztypes.webhooks import Webhook as BASE_WEBHOOK
|
||||||
from onefuzztypes.webhooks import WebhookMessage
|
from onefuzztypes.webhooks import (
|
||||||
|
WebhookMessage,
|
||||||
|
WebhookMessageEventGrid,
|
||||||
|
WebhookMessageFormat,
|
||||||
|
)
|
||||||
from onefuzztypes.webhooks import WebhookMessageLog as BASE_WEBHOOK_MESSAGE_LOG
|
from onefuzztypes.webhooks import WebhookMessageLog as BASE_WEBHOOK_MESSAGE_LOG
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
@ -203,6 +208,7 @@ class Webhook(BASE_WEBHOOK, ORMMixin):
|
|||||||
event_type=message_log.event_type,
|
event_type=message_log.event_type,
|
||||||
event=message_log.event,
|
event=message_log.event,
|
||||||
secret_token=self.secret_token,
|
secret_token=self.secret_token,
|
||||||
|
message_format=self.message_format,
|
||||||
)
|
)
|
||||||
|
|
||||||
headers = {"Content-type": "application/json", "User-Agent": USER_AGENT}
|
headers = {"Content-type": "application/json", "User-Agent": USER_AGENT}
|
||||||
@ -225,7 +231,24 @@ def build_message(
|
|||||||
event_type: EventType,
|
event_type: EventType,
|
||||||
event: Event,
|
event: Event,
|
||||||
secret_token: Optional[str] = None,
|
secret_token: Optional[str] = None,
|
||||||
|
message_format: Optional[WebhookMessageFormat] = None,
|
||||||
) -> Tuple[bytes, Optional[str]]:
|
) -> Tuple[bytes, Optional[str]]:
|
||||||
|
|
||||||
|
if message_format and message_format == WebhookMessageFormat.event_grid:
|
||||||
|
decoded = [
|
||||||
|
json.loads(
|
||||||
|
WebhookMessageEventGrid(
|
||||||
|
id=event_id,
|
||||||
|
data=event,
|
||||||
|
dataVersion="1.0.0",
|
||||||
|
subject=get_instance_name(),
|
||||||
|
eventType=event_type,
|
||||||
|
eventTime=datetime.datetime.now(datetime.timezone.utc),
|
||||||
|
).json(sort_keys=True, exclude_none=True)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
data = json.dumps(decoded).encode()
|
||||||
|
else:
|
||||||
data = (
|
data = (
|
||||||
WebhookMessage(
|
WebhookMessage(
|
||||||
webhook_id=webhook_id,
|
webhook_id=webhook_id,
|
||||||
|
@ -51,6 +51,9 @@ def post(req: func.HttpRequest) -> func.HttpResponse:
|
|||||||
event_types=request.event_types,
|
event_types=request.event_types,
|
||||||
secret_token=request.secret_token,
|
secret_token=request.secret_token,
|
||||||
)
|
)
|
||||||
|
if request.message_format is not None:
|
||||||
|
webhook.message_format = request.message_format
|
||||||
|
|
||||||
webhook.save()
|
webhook.save()
|
||||||
|
|
||||||
webhook.url = None
|
webhook.url = None
|
||||||
@ -83,6 +86,9 @@ def patch(req: func.HttpRequest) -> func.HttpResponse:
|
|||||||
if request.secret_token is not None:
|
if request.secret_token is not None:
|
||||||
webhook.secret_token = request.secret_token
|
webhook.secret_token = request.secret_token
|
||||||
|
|
||||||
|
if request.message_format is not None:
|
||||||
|
webhook.message_format = request.message_format
|
||||||
|
|
||||||
webhook.save()
|
webhook.save()
|
||||||
webhook.url = None
|
webhook.url = None
|
||||||
webhook.secret_token = None
|
webhook.secret_token = None
|
||||||
|
@ -326,6 +326,7 @@ class Webhooks(Endpoint):
|
|||||||
event_types: List[events.EventType],
|
event_types: List[events.EventType],
|
||||||
*,
|
*,
|
||||||
secret_token: Optional[str] = None,
|
secret_token: Optional[str] = None,
|
||||||
|
message_format: Optional[webhooks.WebhookMessageFormat] = None,
|
||||||
) -> webhooks.Webhook:
|
) -> webhooks.Webhook:
|
||||||
"""Create a webhook"""
|
"""Create a webhook"""
|
||||||
self.logger.debug("creating webhook. name: %s", name)
|
self.logger.debug("creating webhook. name: %s", name)
|
||||||
@ -333,7 +334,11 @@ class Webhooks(Endpoint):
|
|||||||
"POST",
|
"POST",
|
||||||
webhooks.Webhook,
|
webhooks.Webhook,
|
||||||
data=requests.WebhookCreate(
|
data=requests.WebhookCreate(
|
||||||
name=name, url=url, event_types=event_types, secret_token=secret_token
|
name=name,
|
||||||
|
url=url,
|
||||||
|
event_types=event_types,
|
||||||
|
secret_token=secret_token,
|
||||||
|
message_format=message_format,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -345,6 +350,7 @@ class Webhooks(Endpoint):
|
|||||||
url: Optional[str] = None,
|
url: Optional[str] = None,
|
||||||
event_types: Optional[List[events.EventType]] = None,
|
event_types: Optional[List[events.EventType]] = None,
|
||||||
secret_token: Optional[str] = None,
|
secret_token: Optional[str] = None,
|
||||||
|
message_format: Optional[webhooks.WebhookMessageFormat] = None,
|
||||||
) -> webhooks.Webhook:
|
) -> webhooks.Webhook:
|
||||||
"""Update a webhook"""
|
"""Update a webhook"""
|
||||||
|
|
||||||
@ -362,6 +368,7 @@ class Webhooks(Endpoint):
|
|||||||
url=url,
|
url=url,
|
||||||
event_types=event_types,
|
event_types=event_types,
|
||||||
secret_token=secret_token,
|
secret_token=secret_token,
|
||||||
|
message_format=message_format,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
# Licensed under the MIT License.
|
# Licensed under the MIT License.
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
import sys
|
import sys
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
@ -65,7 +67,7 @@ from onefuzztypes.models import (
|
|||||||
UserInfo,
|
UserInfo,
|
||||||
)
|
)
|
||||||
from onefuzztypes.primitives import Container, PoolName, Region
|
from onefuzztypes.primitives import Container, PoolName, Region
|
||||||
from onefuzztypes.webhooks import WebhookMessage
|
from onefuzztypes.webhooks import WebhookMessage, WebhookMessageEventGrid
|
||||||
|
|
||||||
EMPTY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
EMPTY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
ZERO_SHA256 = "0" * len(EMPTY_SHA256)
|
ZERO_SHA256 = "0" * len(EMPTY_SHA256)
|
||||||
@ -290,6 +292,25 @@ def main() -> None:
|
|||||||
instance_name="example",
|
instance_name="example",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
message_event_grid = WebhookMessageEventGrid(
|
||||||
|
dataVersion="1.0.0",
|
||||||
|
subject="example",
|
||||||
|
eventType=EventType.ping,
|
||||||
|
eventTime=datetime.datetime.min,
|
||||||
|
id=UUID(int=0),
|
||||||
|
data=EventPing(ping_id=UUID(int=0)),
|
||||||
|
)
|
||||||
|
|
||||||
|
message_event_grid_json = json.dumps(
|
||||||
|
[
|
||||||
|
json.loads(
|
||||||
|
message_event_grid.json(indent=4, exclude_none=True, sort_keys=True)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
indent=4,
|
||||||
|
sort_keys=True,
|
||||||
|
)
|
||||||
|
|
||||||
result = ""
|
result = ""
|
||||||
result += layer(
|
result += layer(
|
||||||
1,
|
1,
|
||||||
@ -309,6 +330,21 @@ def main() -> None:
|
|||||||
message.json(indent=4, exclude_none=True, sort_keys=True),
|
message.json(indent=4, exclude_none=True, sort_keys=True),
|
||||||
"json",
|
"json",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
result += layer(
|
||||||
|
2,
|
||||||
|
"Event Grid Payload format",
|
||||||
|
"If webhook is set to have Event Grid message format then "
|
||||||
|
"the payload will look as follows:",
|
||||||
|
)
|
||||||
|
|
||||||
|
result += typed(
|
||||||
|
3,
|
||||||
|
"Example",
|
||||||
|
message_event_grid_json,
|
||||||
|
"json",
|
||||||
|
)
|
||||||
|
|
||||||
result += layer(2, "Event Types (EventType)")
|
result += layer(2, "Event Types (EventType)")
|
||||||
|
|
||||||
event_map = {get_event_type(x).name: x for x in examples}
|
event_map = {get_event_type(x).name: x for x in examples}
|
||||||
|
@ -22,6 +22,7 @@ from .enums import (
|
|||||||
from .events import EventType
|
from .events import EventType
|
||||||
from .models import AutoScaleConfig, InstanceConfig, NotificationConfig
|
from .models import AutoScaleConfig, InstanceConfig, NotificationConfig
|
||||||
from .primitives import Container, PoolName, Region
|
from .primitives import Container, PoolName, Region
|
||||||
|
from .webhooks import WebhookMessageFormat
|
||||||
|
|
||||||
|
|
||||||
class BaseRequest(BaseModel):
|
class BaseRequest(BaseModel):
|
||||||
@ -211,6 +212,7 @@ class WebhookCreate(BaseRequest):
|
|||||||
url: AnyHttpUrl
|
url: AnyHttpUrl
|
||||||
event_types: List[EventType]
|
event_types: List[EventType]
|
||||||
secret_token: Optional[str]
|
secret_token: Optional[str]
|
||||||
|
message_format: Optional[WebhookMessageFormat]
|
||||||
|
|
||||||
|
|
||||||
class WebhookSearch(BaseModel):
|
class WebhookSearch(BaseModel):
|
||||||
@ -227,6 +229,7 @@ class WebhookUpdate(BaseModel):
|
|||||||
event_types: Optional[List[EventType]]
|
event_types: Optional[List[EventType]]
|
||||||
url: Optional[AnyHttpUrl]
|
url: Optional[AnyHttpUrl]
|
||||||
secret_token: Optional[str]
|
secret_token: Optional[str]
|
||||||
|
message_format: Optional[WebhookMessageFormat]
|
||||||
|
|
||||||
|
|
||||||
class NodeAddSshKey(BaseModel):
|
class NodeAddSshKey(BaseModel):
|
||||||
|
@ -3,19 +3,35 @@
|
|||||||
# Copyright (c) Microsoft Corporation.
|
# Copyright (c) Microsoft Corporation.
|
||||||
# Licensed under the MIT License.
|
# Licensed under the MIT License.
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from enum import Enum
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
from pydantic import AnyHttpUrl, BaseModel, Field
|
from pydantic import AnyHttpUrl, BaseModel, Field
|
||||||
|
|
||||||
from .enums import WebhookMessageState
|
from .enums import WebhookMessageState
|
||||||
from .events import EventMessage, EventType
|
from .events import Event, EventMessage, EventType
|
||||||
|
|
||||||
|
|
||||||
|
class WebhookMessageFormat(Enum):
|
||||||
|
onefuzz = "onefuzz"
|
||||||
|
event_grid = "event_grid"
|
||||||
|
|
||||||
|
|
||||||
class WebhookMessage(EventMessage):
|
class WebhookMessage(EventMessage):
|
||||||
webhook_id: UUID
|
webhook_id: UUID
|
||||||
|
|
||||||
|
|
||||||
|
class WebhookMessageEventGrid(BaseModel):
|
||||||
|
dataVersion: str
|
||||||
|
subject: str
|
||||||
|
eventType: EventType
|
||||||
|
eventTime: datetime
|
||||||
|
id: UUID
|
||||||
|
data: Event
|
||||||
|
|
||||||
|
|
||||||
class WebhookMessageLog(WebhookMessage):
|
class WebhookMessageLog(WebhookMessage):
|
||||||
state: WebhookMessageState = Field(default=WebhookMessageState.queued)
|
state: WebhookMessageState = Field(default=WebhookMessageState.queued)
|
||||||
try_count: int = Field(default=0)
|
try_count: int = Field(default=0)
|
||||||
@ -27,3 +43,4 @@ class Webhook(BaseModel):
|
|||||||
url: Optional[AnyHttpUrl]
|
url: Optional[AnyHttpUrl]
|
||||||
event_types: List[EventType]
|
event_types: List[EventType]
|
||||||
secret_token: Optional[str]
|
secret_token: Optional[str]
|
||||||
|
message_format: Optional[WebhookMessageFormat]
|
||||||
|
Reference in New Issue
Block a user