Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Azure DevOps (f.k.a. VSTS) receiver #328

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions src/WebHooks/WebHooks.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27110.0
# Visual Studio Version 16
VisualStudioVersion = 16.0.30711.63
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Src", "Src", "{929F44D0-A040-4DC3-A22F-4C5829C05D44}"
ProjectSection(SolutionItems) = preProject
Expand Down Expand Up @@ -35,7 +35,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GitHubCoreReceiver", "sampl
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebHooks.Receivers.AzureAlert", "src\Microsoft.AspNetCore.WebHooks.Receivers.AzureAlert\Microsoft.AspNetCore.WebHooks.Receivers.AzureAlert.csproj", "{FAE4E7F8-7786-4388-802D-69C5C40DD5ED}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.WebHooks.Receivers.AzureContainerRegistry", "src\Microsoft.AspNetCore.WebHooks.Receivers.AzureContainerRegistry\Microsoft.AspNetCore.WebHooks.Receivers.AzureContainerRegistry.csproj", "{B4297872-1ED3-4A5D-95A8-0C1A11FDF781}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebHooks.Receivers.AzureContainerRegistry", "src\Microsoft.AspNetCore.WebHooks.Receivers.AzureContainerRegistry\Microsoft.AspNetCore.WebHooks.Receivers.AzureContainerRegistry.csproj", "{B4297872-1ED3-4A5D-95A8-0C1A11FDF781}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebHooks.Receivers.BitBucket", "src\Microsoft.AspNetCore.WebHooks.Receivers.BitBucket\Microsoft.AspNetCore.WebHooks.Receivers.BitBucket.csproj", "{06B0D9D1-1368-423E-B2F7-5F408423A187}"
EndProject
Expand Down Expand Up @@ -99,6 +99,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TrelloCoreReceiver", "sampl
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebHooks.FunctionalTest", "test\Microsoft.AspNetCore.WebHooks.FunctionalTest\Microsoft.AspNetCore.WebHooks.FunctionalTest.csproj", "{F42E56B7-60D4-481A-8585-04015B2B501E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebHooks.Receivers.AzureDevOps", "src\Microsoft.AspNetCore.WebHooks.Receivers.AzureDevOps\Microsoft.AspNetCore.WebHooks.Receivers.AzureDevOps.csproj", "{820D031D-237E-4892-A0BD-EF341421066A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureDevOpsCoreReceiver", "samples\AzureDevOpsCoreReceiver\AzureDevOpsCoreReceiver.csproj", "{DAE01DE1-421B-420C-87DE-74ED9279A034}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -249,6 +253,14 @@ Global
{F42E56B7-60D4-481A-8585-04015B2B501E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F42E56B7-60D4-481A-8585-04015B2B501E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F42E56B7-60D4-481A-8585-04015B2B501E}.Release|Any CPU.Build.0 = Release|Any CPU
{820D031D-237E-4892-A0BD-EF341421066A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{820D031D-237E-4892-A0BD-EF341421066A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{820D031D-237E-4892-A0BD-EF341421066A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{820D031D-237E-4892-A0BD-EF341421066A}.Release|Any CPU.Build.0 = Release|Any CPU
{DAE01DE1-421B-420C-87DE-74ED9279A034}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DAE01DE1-421B-420C-87DE-74ED9279A034}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DAE01DE1-421B-420C-87DE-74ED9279A034}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DAE01DE1-421B-420C-87DE-74ED9279A034}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -290,6 +302,8 @@ Global
{91E02D64-BA6E-473C-A250-D1051A50DDAB} = {9575CB90-BC4B-43BB-8AEA-82C53FDA4187}
{E9E37253-3FE1-44A4-A9E7-A2B6C54EECFE} = {E957C8D9-B4A0-488B-838F-BAB4DE080A76}
{F42E56B7-60D4-481A-8585-04015B2B501E} = {9575CB90-BC4B-43BB-8AEA-82C53FDA4187}
{820D031D-237E-4892-A0BD-EF341421066A} = {929F44D0-A040-4DC3-A22F-4C5829C05D44}
{DAE01DE1-421B-420C-87DE-74ED9279A034} = {E957C8D9-B4A0-488B-838F-BAB4DE080A76}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {12CE6238-F847-4984-8622-1ED46072150A}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks>$(StandardTestWebsiteTfms)</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.DataAnnotations" Version="$(MicrosoftAspNetCoreMvcDataAnnotationsPackageVersion)" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.WebHooks.Receivers.AzureDevOps\Microsoft.AspNetCore.WebHooks.Receivers.AzureDevOps.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebHooks;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;

namespace AzureDevOpsStronglyTypedCoreReceiver.Controllers
{
public class AzureDevOpsController : ControllerBase
{
private readonly ILogger _logger;

public AzureDevOpsController(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<AzureDevOpsController>();
}

[AzureDevOpsWebHook]
public IActionResult AzureDevOps(string id, string @event, string webHookId, JObject data)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

_logger.LogInformation(
1,
$"{nameof(AzureDevOpsController)} / '{{Id}}' received '{{EventName}}' and '{{WebHookId}}'.",
id,
@event,
webHookId);

return Ok();
}
}
}
17 changes: 17 additions & 0 deletions src/WebHooks/samples/AzureDevOpsCoreReceiver/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;

namespace AzureDevOpsStronglyTypedCoreReceiver
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
}
25 changes: 25 additions & 0 deletions src/WebHooks/samples/AzureDevOpsCoreReceiver/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace AzureDevOpsStronglyTypedCoreReceiver
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvcCore()
.AddAzureDevOpsWebHooks()
.AddDataAnnotations();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc();
}
}
}
16 changes: 16 additions & 0 deletions src/WebHooks/samples/AzureDevOpsCoreReceiver/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"WebHooks:AzureDevOps:SecretKey:default": "01234567890123456789012345678901",
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
},
"Console": {
"LogLevel": {
"AzureDevOpsStronglyTypedCoreReceiver": "Information",
"Default": "Warning",
"Microsoft": "Information"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.AspNetCore.WebHooks
{
/// <summary>
/// Well-known names used in Azure DevOps receivers and handlers.
/// </summary>
public static class AzureDevOpsConstants
{
/// <summary>
/// Gets the JSON path of the property in an Azure DevOps WebHook request body containing the Azure DevOps event
/// type.
/// </summary>
public static string EventBodyPropertyPath => "$['eventType']";

/// <summary>
/// Gets the name of the Azure DevOps WebHook receiver.
/// </summary>
public static string ReceiverName => "azuredevops";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.WebHooks.Filters;
using Microsoft.AspNetCore.WebHooks.Metadata;
using Microsoft.AspNetCore.WebHooks.Properties;

namespace Microsoft.AspNetCore.WebHooks
{
/// <summary>
/// <para>
/// An <see cref="Attribute"/> indicating the associated action is an Azure DevOps WebHook endpoint. Specifies the
/// optional <see cref="WebHookAttribute.Id"/>. Also adds a <see cref="WebHookReceiverExistsFilter"/> and a
/// <see cref="ModelStateInvalidFilter"/> (unless <see cref="ApiBehaviorOptions.SuppressModelStateInvalidFilter"/>
/// is <see langword="true"/>) for the action.
/// </para>
/// <para>
/// The signature of the action should be:
/// <code>
/// Task{IActionResult} ActionName(string id, string @event, TData data)
/// </code>
/// or include the subset of parameters required. <c>TData</c> must be compatible with expected requests e.g.
/// <see cref="Newtonsoft.Json.Linq.JObject"/> or <see cref="AzureDevOpsNotification"/>.
/// </para>
/// <para>
/// An example Azure DevOps WebHook URI is
/// '<c>https://{host}/api/webhooks/incoming/azuredevops/{id}?code=83699ec7c1d794c0c780e49a5c72972590571fd8</c>'.
/// See
/// <see href="https://docs.microsoft.com/en-us/azure/devops/service-hooks/services/webhooks?view=azure-devops"/>
/// for additional details about Azure DevOps WebHook requests.
/// </para>
/// </summary>
/// <remarks>
/// <para>
/// If the application enables CORS in general (see the <c>Microsoft.AspNetCore.Cors</c> package), apply
/// <c>DisableCorsAttribute</c> to this action. If the application depends on the
/// <c>Microsoft.AspNetCore.Mvc.ViewFeatures</c> package, apply <c>IgnoreAntiforgeryTokenAttribute</c> to this
/// action.
/// </para>
/// <para>
/// <see cref="AzureDevOpsWebHookAttribute"/> should be used at most once per <see cref="WebHookAttribute.Id"/> and
/// <see cref="EventName"/> in a WebHook application.
/// </para>
/// </remarks>
public class AzureDevOpsWebHookAttribute : WebHookAttribute
{
/// <summary>
/// Instantiates a new <see cref="AzureDevOpsWebHookAttribute"/> indicating the associated action is an Azure
/// DevOps WebHook endpoint.
/// </summary>
public AzureDevOpsWebHookAttribute()
: base(AzureDevOpsConstants.ReceiverName)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Newtonsoft.Json;

namespace Microsoft.AspNetCore.WebHooks
{
/// <summary>
/// Root object of payload sent for all types of events.
/// </summary>
/// <typeparam name="T">Type of resource within payload which differs depending on '<c>eventType</c>' field</typeparam>
public abstract class BasePayload<T> where T : BaseResource
{
/// <summary>
/// Gets the subscription identifier which triggered the event.
/// </summary>
[JsonProperty("subscriptionId")]
public string SubscriptionId { get; set; }

/// <summary>
/// Gets the notification identifier within subscription.
/// </summary>
[JsonProperty("notificationId")]
public int NotificationId { get; set; }

/// <summary>
/// Gets the identifier of HTTP request.
/// </summary>
[JsonProperty("id")]
public string Id { get; set; }

/// <summary>
/// Gets the type of the event.
/// </summary>
[JsonProperty("eventType")]
public string EventType { get; set; }

/// <summary>
/// Gets the publisher identifier.
/// </summary>
[JsonProperty("publisherId")]
public string PublisherId { get; set; }

/// <summary>
/// Gets the message which describes the event.
/// </summary>
[JsonProperty("message")]
public PayloadMessage Message { get; set; }

/// <summary>
/// Gets the detailed message which describes the event.
/// </summary>
[JsonProperty("detailedMessage")]
public PayloadMessage DetailedMessage { get; set; }

/// <summary>
/// Gets the resource itself - data associated with corresponding event.
/// </summary>
[JsonProperty("resource")]
public T Resource { get; set; }

/// <summary>
/// Gets the resource version.
/// </summary>
[JsonProperty("resourceVersion")]
public string ResourceVersion { get; set; }

/// <summary>
/// Gets the resource containers.
/// </summary>
[JsonProperty("resourceContainers")]
public PayloadResourceContainers ResourceContainers { get; set; }

/// <summary>
/// Gets the date when HTTP request was created.
/// </summary>
[JsonProperty("createdDate")]
public DateTime CreatedDate { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.AspNetCore.WebHooks
{
/// <summary>
/// Base class for resource object which describes
/// a specific event type.
/// </summary>
public abstract class BaseResource
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Newtonsoft.Json;

namespace Microsoft.AspNetCore.WebHooks
{
/// <summary>
/// Base class for resource object which describes WorkItem event types.
/// </summary>
/// <typeparam name="T">Type which describes fields associated with this kind of WorkItem change</typeparam>
public abstract class BaseWorkItemResource<T> : BaseResource
{
/// <summary>
/// Gets the identifier of WorkItem.
/// </summary>
[JsonProperty("id")]
public int Id { get; set; }

/// <summary>
/// Gets the revision number.
/// </summary>
[JsonProperty("rev")]
public int RevisionNumber { get; set; }

/// <summary>
/// Gets fields associated with the WorkItem.
/// </summary>
[JsonProperty("fields")]
public T Fields { get; set; }

/// <summary>
/// Gets links associated with the WorkItem.
/// </summary>
[JsonProperty("_links")]
public WorkItemLinks Links { get; set; }

/// <summary>
/// Gets the URL of the WorkItem.
/// </summary>
[JsonProperty("url")]
public Uri Url { get; set; }
}
}
Loading