public static async Task LockHttpOperationAsync(this IOperationLock lockObj, Func <Task> action) { bool acquired = await lockObj.TryLockOperationAsync(action, TimeSpan.Zero); if (!acquired) { var response = new HttpResponseMessage(HttpStatusCode.Conflict); response.Content = new StringContent(Resources.Error_DeploymentInProgess); throw new HttpResponseException(response); } }
public async Task PublishEventAsync(string hookEventType, object eventContent) { using (_tracer.Step("WebHooksManager.PublishEventAsync: " + hookEventType)) { string jsonString = JsonConvert.SerializeObject(eventContent, JsonSerializerSettings); bool lockAcquired = await _hooksLock.TryLockOperationAsync(async() => { await PublishToHooksAsync(jsonString, hookEventType); }, LockTimeout); VerifyLockAcquired(lockAcquired); } }
public override async Task ProcessRequestAsync(HttpContext context) { using (_tracer.Step("FetchHandler")) { // Redirect GET /deploy requests to the Kudu root for convenience when using URL from Azure portal if (String.Equals(context.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) { context.Response.Redirect("~/"); context.ApplicationInstance.CompleteRequest(); return; } if (!String.Equals(context.Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase)) { context.Response.StatusCode = (int)HttpStatusCode.NotFound; context.ApplicationInstance.CompleteRequest(); return; } context.Response.TrySkipIisCustomErrors = true; DeploymentInfo deployInfo = null; // We are going to assume that the branch details are already set by the time it gets here. This is particularly important in the mercurial case, // since Settings hardcodes the default value for Branch to be "master". Consequently, Kudu will NoOp requests for Mercurial commits. string targetBranch = _settings.GetBranch(); try { var request = new HttpRequestWrapper(context.Request); JObject payload = GetPayload(request); DeployAction action = GetRepositoryInfo(request, payload, targetBranch, out deployInfo); if (action == DeployAction.NoOp) { return; } // If Scm is not enabled, we will reject all but one payload for GenericHandler // This is to block the unintended CI with Scm providers like GitHub // Since Generic payload can only be done by user action, we loosely allow // that and assume users know what they are doing. Same applies to git // push/clone endpoint. if (!_settings.IsScmEnabled() && !(deployInfo.Handler is GenericHandler || deployInfo.Handler is DropboxHandler)) { context.Response.StatusCode = (int)HttpStatusCode.Forbidden; context.ApplicationInstance.CompleteRequest(); return; } } catch (FormatException ex) { _tracer.TraceError(ex); context.Response.StatusCode = 400; context.Response.Write(ex.Message); context.ApplicationInstance.CompleteRequest(); return; } _tracer.Trace("Attempting to fetch target branch {0}", targetBranch); bool acquired = await _deploymentLock.TryLockOperationAsync(async() => { await PerformDeployment(deployInfo); }, TimeSpan.Zero); if (!acquired) { // Create a marker file that indicates if there's another deployment to pull // because there was a deployment in progress. using (_tracer.Step("Update pending deployment marker file")) { // REVIEW: This makes the assumption that the repository url is the same. // If it isn't the result would be buggy either way. FileSystemHelpers.SetLastWriteTimeUtc(_markerFilePath, DateTime.UtcNow); } // Return a http 202: the request has been accepted for processing, but the processing has not been completed. context.Response.StatusCode = (int)HttpStatusCode.Accepted; context.ApplicationInstance.CompleteRequest(); } } }
public override async Task ProcessRequestAsync(HttpContext context) { using (_tracer.Step("FetchHandler")) { // Redirect GET /deploy requests to the Kudu root for convenience when using URL from Azure portal if (String.Equals(context.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) { context.Response.Redirect("~/"); context.ApplicationInstance.CompleteRequest(); return; } if (!String.Equals(context.Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase)) { context.Response.StatusCode = (int)HttpStatusCode.NotFound; context.ApplicationInstance.CompleteRequest(); return; } context.Response.TrySkipIisCustomErrors = true; DeploymentInfo deployInfo = null; // We are going to assume that the branch details are already set by the time it gets here. This is particularly important in the mercurial case, // since Settings hardcodes the default value for Branch to be "master". Consequently, Kudu will NoOp requests for Mercurial commits. string targetBranch = _settings.GetBranch(); try { var request = new HttpRequestWrapper(context.Request); JObject payload = GetPayload(request); DeployAction action = GetRepositoryInfo(request, payload, targetBranch, out deployInfo); if (action == DeployAction.NoOp) { return; } // If Scm is not enabled, we will reject all but one payload for GenericHandler // This is to block the unintended CI with Scm providers like GitHub // Since Generic payload can only be done by user action, we loosely allow // that and assume users know what they are doing. Same applies to git // push/clone endpoint. if (!_settings.IsScmEnabled() && !(deployInfo.Handler is GenericHandler || deployInfo.Handler is DropboxHandler)) { context.Response.StatusCode = (int)HttpStatusCode.Forbidden; context.ApplicationInstance.CompleteRequest(); return; } } catch (FormatException ex) { _tracer.TraceError(ex); context.Response.StatusCode = 400; context.Response.Write(ex.Message); context.ApplicationInstance.CompleteRequest(); return; } // for CI payload, we will return Accepted and do the task in the BG // if isAsync is defined, we will return Accepted and do the task in the BG // since autoSwap relies on the response header, deployment has to be synchronously. bool isAsync = String.Equals(context.Request.QueryString["isAsync"], "true", StringComparison.OrdinalIgnoreCase); bool isBackground = (isAsync || deployInfo.IsContinuous) && !_autoSwapHandler.IsAutoSwapEnabled(); if (isBackground) { using (_tracer.Step("Start deployment in the background")) { ChangeSet tempChangeSet = null; IDisposable tempDeployment = null; if (isAsync) { // create temporary deployment before the actual deployment item started // this allows portal ui to readily display on-going deployment (not having to wait for fetch to complete). // in addition, it captures any failure that may occur before the actual deployment item started tempDeployment = _deploymentManager.CreateTemporaryDeployment( Resources.ReceivingChanges, out tempChangeSet, deployInfo.TargetChangeset, deployInfo.Deployer); } PerformBackgroundDeployment(deployInfo, _environment, _settings, _tracer.TraceLevel, context.Request.Url, tempDeployment, tempChangeSet); } // to avoid regression, only set location header if isAsync if (isAsync) { // latest deployment keyword reserved to poll till deployment done context.Response.Headers["Location"] = new Uri(context.Request.Url, String.Format("/api/deployments/{0}?deployer={1}&time={2}", Constants.LatestDeployment, deployInfo.Deployer, DateTime.UtcNow.ToString("yyy-MM-dd_HH-mm-ssZ"))).ToString(); } context.Response.StatusCode = (int)HttpStatusCode.Accepted; context.ApplicationInstance.CompleteRequest(); return; } _tracer.Trace("Attempting to fetch target branch {0}", targetBranch); bool acquired = await _deploymentLock.TryLockOperationAsync(async() => { if (_autoSwapHandler.IsAutoSwapOngoing()) { context.Response.StatusCode = (int)HttpStatusCode.Conflict; context.Response.Write(Resources.Error_AutoSwapDeploymentOngoing); context.ApplicationInstance.CompleteRequest(); return; } await PerformDeployment(deployInfo); _autoSwapHandler.HandleAutoSwap(); }, TimeSpan.Zero); if (!acquired) { // Create a marker file that indicates if there's another deployment to pull // because there was a deployment in progress. using (_tracer.Step("Update pending deployment marker file")) { // REVIEW: This makes the assumption that the repository url is the same. // If it isn't the result would be buggy either way. FileSystemHelpers.SetLastWriteTimeUtc(_markerFilePath, DateTime.UtcNow); } // Return a http 202: the request has been accepted for processing, but the processing has not been completed. context.Response.StatusCode = (int)HttpStatusCode.Accepted; context.ApplicationInstance.CompleteRequest(); } } }