From d84ee24b6d210fe144e4c91762de8659e63e0684 Mon Sep 17 00:00:00 2001 From: Xiaomin Wu Date: Mon, 12 Oct 2015 16:54:02 -0700 Subject: [PATCH] Post comment as notification if is deploy from a PR. --- Slingshot.Api/Abstract/Repository.cs | 5 +++ Slingshot.Api/App_Start/WebApiConfig.cs | 1 + Slingshot.Api/Concrete/BitbucketRepository.cs | 26 +++++++++++++-- Slingshot.Api/Controllers/ARMController.cs | 32 ++++++++++++++++++- Slingshot.Api/Helpers/Constants.cs | 1 + .../Models/DeploymentNotificationInputs.cs | 13 ++++++++ Slingshot.Api/Slingshot.Api.csproj | 1 + Slingshot.Api/ng/Scripts/app.js | 11 +++++++ 8 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 Slingshot.Api/Models/DeploymentNotificationInputs.cs diff --git a/Slingshot.Api/Abstract/Repository.cs b/Slingshot.Api/Abstract/Repository.cs index bf91450..f83003e 100644 --- a/Slingshot.Api/Abstract/Repository.cs +++ b/Slingshot.Api/Abstract/Repository.cs @@ -146,6 +146,11 @@ public virtual Task HasScmInfo() return Task.FromResult(false); } + public virtual Task WritePullRequestComment(string prId, string comment) + { + return Task.FromResult(new object()); + } + /// /// Pubic repo should always return true, private repo and use`s access token has access to the repo, return true /// All other case return false, e.g: diff --git a/Slingshot.Api/App_Start/WebApiConfig.cs b/Slingshot.Api/App_Start/WebApiConfig.cs index 51cfc76..d2003a3 100644 --- a/Slingshot.Api/App_Start/WebApiConfig.cs +++ b/Slingshot.Api/App_Start/WebApiConfig.cs @@ -19,6 +19,7 @@ public static void Register(HttpConfiguration config) config.Routes.MapHttpRoute("post-deployments", "api/deployments/{subscriptionId}", new { controller = "ARM", action = "Deploy" }, new { verb = new HttpMethodConstraint("POST") }); config.Routes.MapHttpRoute("get-deployments-status", "api/deployments/{subscriptionId}/rg/{resourceGroup}", new { controller = "ARM", action = "GetDeploymentStatus" }, new { verb = new HttpMethodConstraint("GET") }); config.Routes.MapHttpRoute("get-scmdeployments-status", "api/deployments/{subscriptionId}/rg/{resourceGroup}/scm", new { controller = "ARM", action = "GetScmDeploymentStatus" }, new { verb = new HttpMethodConstraint("GET") }); + config.Routes.MapHttpRoute("post-deployments-notification", "api/deploymentsnotification", new { controller = "ARM", action = "DeploymentNotification" }, new { verb = new HttpMethodConstraint("POST") }); config.Routes.MapHttpRoute("get", "api/{*path}", new { controller = "ARM", action = "Get" }, new { verb = new HttpMethodConstraint("GET", "HEAD") }); diff --git a/Slingshot.Api/Concrete/BitbucketRepository.cs b/Slingshot.Api/Concrete/BitbucketRepository.cs index 5812499..bebee25 100644 --- a/Slingshot.Api/Concrete/BitbucketRepository.cs +++ b/Slingshot.Api/Concrete/BitbucketRepository.cs @@ -2,8 +2,6 @@ using System.Globalization; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; using System.Threading.Tasks; using System.Web; using Newtonsoft.Json.Linq; @@ -212,6 +210,30 @@ public override async Task HasAccess() return false; } + public override async Task WritePullRequestComment(string prId, string comment) + { + if (await this.HasScmInfo() == false) + { + throw new InvalidOperationException("User credential is required to post any comments for a Pull Requests."); + } + + try + { + SourceControlInfo info = await this.GetScmInfo(); + string repoToken = info.token; + + using (HttpClient client = CreateHttpClient(accessToken: repoToken)) + { + var repoInfoUrl = string.Format(CultureInfo.InvariantCulture, Constants.Repository.BitbucketApiPullRequestCommentsFormat, UserName, RepositoryName, prId); + await client.PostAsJsonAsync(repoInfoUrl, new { content = comment }); + } + } + catch (Exception ex) + { + throw new InvalidOperationException(string.Format("Failed to create comment on Pull Request: {0}. {1}", prId, ex.Message)); + } + } + /// /// Caller need to make sure there is access token when run against private repo, otherwise error will be thrown /// diff --git a/Slingshot.Api/Controllers/ARMController.cs b/Slingshot.Api/Controllers/ARMController.cs index d2d7420..ebd2f45 100644 --- a/Slingshot.Api/Controllers/ARMController.cs +++ b/Slingshot.Api/Controllers/ARMController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -7,6 +8,7 @@ using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; +using System.Web; using System.Web.Http; using System.Web.Http.Routing; using Microsoft.Azure.Management.Resources; @@ -18,7 +20,6 @@ using Slingshot.Concrete; using Slingshot.Helpers; using Slingshot.Models; -using System.Web; namespace Slingshot.Controllers { @@ -395,6 +396,35 @@ public async Task GetTemplate(string repositoryUrl) return response; } + [Authorize] + [HttpPost] + public async Task DeploymentNotification(DeploymentNotificationInputs inputs) + { + string repositoryUrl = HttpUtility.UrlDecode(inputs.deployInputs.repoUrl); + string token = GetTokenFromHeader(); + Repository repo = Repository.CreateRepositoryObj(repositoryUrl, Request.RequestUri.Host, token); + var queryStrings = HttpUtility.ParseQueryString(repositoryUrl); + if (queryStrings["pr"] != null) + { + // if deployment is come from a pull request, post a comment back to the pull request. + string siteUrl = inputs.siteUrl; + StringBuilder pullRequestComment = new StringBuilder(); + pullRequestComment.AppendFormat(CultureInfo.InvariantCulture, "A [website]({0}) has been deployed to Azure from this pull request", siteUrl); + + bool isManualIntegration = true; + if (inputs.deployInputs.parameters["isManualIntegration"] != null && + inputs.deployInputs.parameters["isManualIntegration"]["value"] != null && + bool.TryParse(inputs.deployInputs.parameters["isManualIntegration"]["value"].ToString(), out isManualIntegration) && + !isManualIntegration) + { + pullRequestComment.Append(" with continuous deployment enabled"); + } + + pullRequestComment.AppendFormat(". {0}", siteUrl); + await repo.WritePullRequestComment(queryStrings["pr"], pullRequestComment.ToString()); + } + } + private async Task GenerateResourceGroupName(string token, Repository repo, SubscriptionInfo[] subscriptions) { if (!string.IsNullOrEmpty(repo.RepositoryName)) diff --git a/Slingshot.Api/Helpers/Constants.cs b/Slingshot.Api/Helpers/Constants.cs index 41247c3..bf51996 100644 --- a/Slingshot.Api/Helpers/Constants.cs +++ b/Slingshot.Api/Helpers/Constants.cs @@ -19,6 +19,7 @@ public class Repository public const string BitbucketApiRepoInfoFormat = "https://api.bitbucket.org/2.0/repositories/{0}/{1}"; public const string BitbucketRawFileWebFormat = "https://bitbucket.org/{0}/{1}/raw/{2}/{3}"; public const string BitbucketApiMainBranchInfoFormat = "https://bitbucket.org/api/1.0/repositories/{0}/{1}/main-branch"; + public const string BitbucketApiPullRequestCommentsFormat = "https://bitbucket.org/api/1.0/repositories/{0}/{1}/pullrequests/{2}/comments"; public const string BitbucketApiRawFile = "https://bitbucket.org/api/1.0/repositories/{0}/{1}/raw/{2}/{3}"; } diff --git a/Slingshot.Api/Models/DeploymentNotificationInputs.cs b/Slingshot.Api/Models/DeploymentNotificationInputs.cs new file mode 100644 index 0000000..5945fa1 --- /dev/null +++ b/Slingshot.Api/Models/DeploymentNotificationInputs.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace Slingshot.Models +{ + public class DeploymentNotificationInputs + { + public string siteUrl { get; set; } + public DeployInputs deployInputs { get; set; } + } +} \ No newline at end of file diff --git a/Slingshot.Api/Slingshot.Api.csproj b/Slingshot.Api/Slingshot.Api.csproj index 3fbb676..a889343 100644 --- a/Slingshot.Api/Slingshot.Api.csproj +++ b/Slingshot.Api/Slingshot.Api.csproj @@ -175,6 +175,7 @@ + diff --git a/Slingshot.Api/ng/Scripts/app.js b/Slingshot.Api/ng/Scripts/app.js index 11c3824..667371e 100644 --- a/Slingshot.Api/ng/Scripts/app.js +++ b/Slingshot.Api/ng/Scripts/app.js @@ -892,6 +892,17 @@ function IsSiteLocationParam(paramName) { if (result.data.status === 4) { formData.deploymentSucceeded = true; telemetry.logDeploySucceeded(formData.repositoryUrl); + + // once deployment is successfully done + // make a "fire and forget" request to see if there is any post deployment action need to be done + $http({ + method: "post", + url: "api/deploymentsnotification", + data: { + siteUrl: $scope.formData.siteUrl, + deployInputs: $scope.formData.deployPayload + } + }); } else if (result.data.status === 3) { formData.errorMesg = "Failed to acquire content from your repository.";