Skip to content

Commit

Permalink
support VSO notification
Browse files Browse the repository at this point in the history
  • Loading branch information
suwatch committed May 11, 2015
1 parent a964cdd commit cb6ae0f
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 1 deletion.
2 changes: 2 additions & 0 deletions Kudu.Services.Test/Kudu.Services.Test.csproj
Expand Up @@ -90,10 +90,12 @@
<Compile Include="TestMessageHandler.cs" />
<Compile Include="VfsControllerFacts.cs" />
<Compile Include="ZipArchiveExtensionFacts.cs" />
<Compile Include="VSOHandlerFacts.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
<EmbeddedResource Include="vsopayload.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Kudu.Contracts\Kudu.Contracts.csproj">
Expand Down
85 changes: 85 additions & 0 deletions Kudu.Services.Test/VSOHandlerFacts.cs
@@ -0,0 +1,85 @@
using System;
using System.IO;
using System.Reflection;
using System.Web;
using Kudu.Services.ServiceHookHandlers;
using Moq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;

namespace Kudu.Services.Test
{
public class VSOHandlerFacts
{
[Fact]
public void VSOHandlerBasic()
{
// Arrange
var payload = GetVSOPayload();
var httpRequest = new Mock<HttpRequestBase>();
var handler = new VSOHandler();

// Act
DeploymentInfo deploymentInfo;
DeployAction result = handler.TryParseDeploymentInfo(httpRequest.Object, payload: payload, targetBranch: "master", deploymentInfo: out deploymentInfo);

// Assert
Assert.Equal(DeployAction.ProcessDeployment, result);
Assert.NotNull(deploymentInfo);
Assert.Equal("this_is_latest_commit", deploymentInfo.CommitId);
Assert.Equal("this_is_latest_commit", deploymentInfo.TargetChangeset.Id);
Assert.Equal("John Smith", deploymentInfo.TargetChangeset.AuthorName);
Assert.Equal("test01@hotmail.com", deploymentInfo.TargetChangeset.AuthorEmail);
Assert.Equal("commit message", deploymentInfo.TargetChangeset.Message);
Assert.Equal(DateTime.Parse("2015-05-08T00:20:59Z"), deploymentInfo.TargetChangeset.Timestamp);

var repositoryUri = new Uri(deploymentInfo.RepositoryUrl);
Assert.Equal("https", repositoryUri.Scheme);
Assert.Equal("test01.vsoalm.tfsallin.net", repositoryUri.Host);
Assert.Equal("/DefaultCollection/_git/testgit01", repositoryUri.AbsolutePath);
Assert.Equal("this_is_vso_token", repositoryUri.UserInfo);
}

[Fact]
public void VSOHandlerMismatchBranch()
{
// Arrange
var payload = GetVSOPayload();
var httpRequest = new Mock<HttpRequestBase>();
var handler = new VSOHandler();

// Act
DeploymentInfo deploymentInfo;
DeployAction result = handler.TryParseDeploymentInfo(httpRequest.Object, payload: payload, targetBranch: "foo", deploymentInfo: out deploymentInfo);

// Assert
Assert.Equal(DeployAction.NoOp, result);
}

[Fact]
public void VSOHandlerUnknownPayload()
{
// Arrange
var payload = new JObject();
var httpRequest = new Mock<HttpRequestBase>();
var handler = new VSOHandler();

// Act
DeploymentInfo deploymentInfo;
DeployAction result = handler.TryParseDeploymentInfo(httpRequest.Object, payload: payload, targetBranch: "master", deploymentInfo: out deploymentInfo);

// Assert
Assert.Equal(DeployAction.UnknownPayload, result);
}

private static JObject GetVSOPayload()
{
Assembly assembly = Assembly.GetExecutingAssembly();
using (var reader = new StreamReader(assembly.GetManifestResourceStream("Kudu.Services.Test.vsopayload.json")))
{
return (JObject)JToken.ReadFrom(new JsonTextReader(reader));
}
}
}
}
94 changes: 94 additions & 0 deletions Kudu.Services.Test/vsopayload.json
@@ -0,0 +1,94 @@
{
"subscriptionId": "5711b3fd-0121-4fcb-8874-40f5a37fc714",
"notificationId": 1,
"id": "4505c95e-bcf6-4354-8b20-769ea5c302d6",
"eventType": "git.push",
"publisherId": "tfs",
"message": null,
"detailedMessage": null,
"resource": {
"commits": [
{
"commitId": "this_is_latest_commit",
"author": {
"name": "John Smith",
"email": "test01@hotmail.com",
"date": "2015-05-08T00:20:59Z"
},
"committer": {
"name": "John Smith",
"email": "test01@hotmail.com",
"date": "2015-05-08T00:20:59Z"
},
"comment": "commit message",
"url": "https://test01.vsoalm.tfsallin.net/DefaultCollection/_apis/git/repositories/9cd8a073-c67d-4725-8870-ce6fda346771/commits/this_is_latest_commit"
}
],
"refUpdates": [
{
"name": "refs/heads/master",
"oldObjectId": "this_is_old_commit",
"newObjectId": "this_is_latest_commit"
}
],
"repository": {
"id": "9cd8a073-c67d-4725-8870-ce6fda346771",
"name": "testgit01",
"url": "https://test01.vsoalm.tfsallin.net/DefaultCollection/_apis/git/repositories/9cd8a073-c67d-4725-8870-ce6fda346771",
"project": {
"id": "5ec775cb-c546-4106-8a62-acd8ffd21339",
"name": "testgit01",
"url": "https://test01.vsoalm.tfsallin.net/DefaultCollection/_apis/projects/5ec775cb-c546-4106-8a62-acd8ffd21339",
"state": "wellFormed"
},
"defaultBranch": "refs/heads/master",
"remoteUrl": "https://test01.vsoalm.tfsallin.net/DefaultCollection/_git/testgit01"
},
"pushedBy": {
"id": "c11fa894-cdcd-4da1-84c8-fe06e7aa17f2",
"displayName": "John Smith",
"uniqueName": "test01@hotmail.com",
"url": "https://test01.vsoalmsps.tfsallin.net/_apis/Identities/c11fa894-cdcd-4da1-84c8-fe06e7aa17f2",
"imageUrl": "https://test01.vsoalm.tfsallin.net/DefaultCollection/_api/_common/identityImage?id=c11fa894-cdcd-4da1-84c8-fe06e7aa17f2"
},
"pushId": 11,
"date": "2015-05-08T00:21:09.7569964Z",
"url": "https://test01.vsoalm.tfsallin.net/DefaultCollection/_apis/git/repositories/9cd8a073-c67d-4725-8870-ce6fda346771/pushes/11",
"_links": {
"self": {
"href": "https://test01.vsoalm.tfsallin.net/DefaultCollection/_apis/git/repositories/9cd8a073-c67d-4725-8870-ce6fda346771/pushes/11"
},
"repository": {
"href": "https://test01.vsoalm.tfsallin.net/DefaultCollection/_apis/git/repositories/9cd8a073-c67d-4725-8870-ce6fda346771"
},
"commits": {
"href": "https://test01.vsoalm.tfsallin.net/DefaultCollection/_apis/git/repositories/9cd8a073-c67d-4725-8870-ce6fda346771/pushes/11/commits"
},
"pusher": {
"href": "https://test01.vsoalmsps.tfsallin.net/_apis/Identities/c11fa894-cdcd-4da1-84c8-fe06e7aa17f2"
},
"refs": {
"href": "https://test01.vsoalm.tfsallin.net/DefaultCollection/_apis/git/repositories/9cd8a073-c67d-4725-8870-ce6fda346771/refs"
}
}
},
"resourceVersion": "1.0",
"resourceContainers": {
"collection": {
"id": "586fa11f-e0c2-43dd-9ce9-12f8b8f21b87"
},
"account": {
"id": "162cc52c-a0f2-42d7-8d80-eb9f10e6cc3d"
},
"project": {
"id": "5ec775cb-c546-4106-8a62-acd8ffd21339"
}
},
"createdDate": "2015-05-08T00:21:11.4874535Z",
"sessionToken": {
"hasError": false,
"token": "this_is_vso_token",
"validTo": "2015-05-08T00:51:11.94Z",
"scope": "vso.code_write"
}
}
1 change: 1 addition & 0 deletions Kudu.Services.Web/App_Start/NinjectServices.cs
Expand Up @@ -285,6 +285,7 @@ private static void RegisterServices(IKernel kernel)
kernel.Bind<IServiceHookHandler>().To<GitlabHqHandler>().InRequestScope();
kernel.Bind<IServiceHookHandler>().To<GitHubCompatHandler>().InRequestScope();
kernel.Bind<IServiceHookHandler>().To<KilnHgHandler>().InRequestScope();
kernel.Bind<IServiceHookHandler>().To<VSOHandler>().InRequestScope();

// SiteExtensions
kernel.Bind<ISiteExtensionManager>().To<SiteExtensionManager>().InRequestScope();
Expand Down
1 change: 1 addition & 0 deletions Kudu.Services/Kudu.Services.csproj
Expand Up @@ -136,6 +136,7 @@
<Compile Include="ServiceHookHandlers\IServiceHookHandler.cs" />
<Compile Include="ServiceHookHandlers\BitbucketHandler.cs" />
<Compile Include="ServiceHookHandlers\GitHubHandler.cs" />
<Compile Include="ServiceHookHandlers\VSOHandler.cs" />
<Compile Include="GitServer\UploadPackHandler.cs" />
<Compile Include="Infrastructure\ShutdownDetector.cs" />
<Compile Include="Infrastructure\DelegatingStream.cs" />
Expand Down
8 changes: 7 additions & 1 deletion Kudu.Services/ServiceHookHandlers/GitlabHqHandler.cs
Expand Up @@ -44,10 +44,16 @@ public class GitlabHqHandler : GitHubCompatHandler
protected override GitDeploymentInfo GetDeploymentInfo(HttpRequestBase request, JObject payload, string targetBranch)
{
var repository = payload.Value<JObject>("repository");
if (repository == null)
{
// doesn't look like GitlabHQ
return null;
}

var userid = payload.Value<int?>("user_id");
var username = payload.Value<string>("user_name");
var url = repository.Value<string>("url");
if (repository == null || userid == null || username == null || url == null)
if (userid == null || username == null || url == null)
{
// doesn't look like GitlabHQ
return null;
Expand Down
91 changes: 91 additions & 0 deletions Kudu.Services/ServiceHookHandlers/VSOHandler.cs
@@ -0,0 +1,91 @@
using System;
using System.Linq;
using System.Web;
using Kudu.Core.SourceControl;
using Newtonsoft.Json.Linq;

namespace Kudu.Services.ServiceHookHandlers
{
/// <summary>
/// Default Servicehook Handler, uses VSO format.
/// </summary>
public class VSOHandler : ServiceHookHandlerBase
{
public override DeployAction TryParseDeploymentInfo(HttpRequestBase request, JObject payload, string targetBranch, out DeploymentInfo deploymentInfo)
{
deploymentInfo = null;

var publisherId = payload.Value<string>("publisherId");
if (String.Equals(publisherId, "tfs", StringComparison.OrdinalIgnoreCase))
{
GitDeploymentInfo gitDeploymentInfo = GetDeploymentInfo(request, payload, targetBranch);
deploymentInfo = gitDeploymentInfo;
return deploymentInfo == null || IsDeleteCommit(gitDeploymentInfo.NewRef) ? DeployAction.NoOp : DeployAction.ProcessDeployment;
}

return DeployAction.UnknownPayload;
}

protected virtual GitDeploymentInfo GetDeploymentInfo(HttpRequestBase request, JObject payload, string targetBranch)
{
// the rest must exist or NullRef.
// this allows us to identify the culprit handler
var sessionToken = payload.Value<JObject>("sessionToken");
var resource = payload.Value<JObject>("resource");
var refUpdates = resource.Value<JArray>("refUpdates");
var repository = resource.Value<JObject>("repository");

// The format of ref is refs/something/something else
// e.g. refs/head/master or refs/head/foo/bar
string branch = refUpdates.Last.Value<string>("name");
// Extract the name from refs/head/master notation.
int secondSlashIndex = branch.IndexOf('/', 5);
branch = branch.Substring(secondSlashIndex + 1);
if (!branch.Equals(targetBranch, StringComparison.OrdinalIgnoreCase))
{
return null;
}

var info = new GitDeploymentInfo { RepositoryType = RepositoryType.Git };
info.Deployer = GetDeployer(request);
info.NewRef = refUpdates.Last.Value<string>("newObjectId");

var builder = new UriBuilder(repository.Value<string>("remoteUrl"));
builder.UserName = sessionToken.Value<string>("token");
builder.Password = String.Empty;
info.RepositoryUrl = builder.Uri.AbsoluteUri;

var commits = resource.Value<JArray>("commits");
info.CommitId = refUpdates.Last.Value<string>("newObjectId");
info.TargetChangeset = ParseChangeSet(info.CommitId, commits);
return info;
}

protected static ChangeSet ParseChangeSet(string id, JArray commits)
{
if (commits == null || !commits.HasValues)
{
return new ChangeSet(id, authorName: null, authorEmail: null, message: null, timestamp: DateTimeOffset.UtcNow);
}
JToken latestCommit = commits.Last;
JObject commitAuthor = latestCommit.Value<JObject>("author");
return new ChangeSet(
id,
commitAuthor.Value<string>("name"),
commitAuthor.Value<string>("email"),
latestCommit.Value<string>("comment"),
commitAuthor.Value<DateTime>("date")
);
}

protected virtual string GetDeployer(HttpRequestBase request)
{
return "VSO";
}

protected static bool IsDeleteCommit(string newRef)
{
return newRef.All(c => c == '0');
}
}
}

0 comments on commit cb6ae0f

Please sign in to comment.