public override void ProcessRequestBase(HttpContextBase context) { using (Tracer.Step("RpcService.ReceivePack")) { // Ensure that the target directory does not have a non-Git repository. IRepository repository = _repositoryFactory.GetRepository(); if (repository != null && repository.RepositoryType != RepositoryType.Git) { context.Response.StatusCode = (int)HttpStatusCode.BadRequest; if (context.ApplicationInstance != null) { context.ApplicationInstance.CompleteRequest(); } return; } try { DeploymentLock.LockOperation(() => { context.Response.ContentType = "application/x-git-receive-pack-result"; if (PostDeploymentHelper.IsAutoSwapOngoing()) { context.Response.StatusCode = (int)HttpStatusCode.Conflict; context.Response.Write(Resources.Error_AutoSwapDeploymentOngoing); context.ApplicationInstance.CompleteRequest(); return; } string username = null; if (AuthUtility.TryExtractBasicAuthUser(context.Request, out username)) { GitServer.SetDeployer(username); } UpdateNoCacheForResponse(context.Response); // This temporary deployment is for ui purposes only, it will always be deleted via finally. ChangeSet tempChangeSet; using (DeploymentManager.CreateTemporaryDeployment(Resources.ReceivingChanges, out tempChangeSet)) { GitServer.Receive(context.Request.GetInputStream(), context.Response.OutputStream); } }, "Handling git receive pack", TimeSpan.Zero); } catch (LockOperationException ex) { context.Response.StatusCode = 409; context.Response.Write(ex.Message); context.ApplicationInstance.CompleteRequest(); } } }
public void IsAutoSwapOngoingOrEnabledTests() { var homePath = System.Environment.GetEnvironmentVariable("HOME"); var tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); try { System.Environment.SetEnvironmentVariable("HOME", tempPath); System.Environment.SetEnvironmentVariable(Constants.WebSiteSwapSlotName, null); var autoSwapLockFile = Path.Combine(tempPath, @"site\locks", PostDeploymentHelper.AutoSwapLockFile); Directory.CreateDirectory(Path.GetDirectoryName(autoSwapLockFile)); Assert.False(PostDeploymentHelper.IsAutoSwapEnabled(), "Autoswap should NOT be enabled"); Assert.False(PostDeploymentHelper.IsAutoSwapOngoing(), "Should not be any autoswap, since it is not enabled"); System.Environment.SetEnvironmentVariable(Constants.WebSiteSwapSlotName, "someslot"); Assert.True(PostDeploymentHelper.IsAutoSwapEnabled(), "Autoswap should be enabled"); Assert.False(PostDeploymentHelper.IsAutoSwapOngoing(), "Should not be any autoswap, since autoswap lock is not acquired by process."); File.WriteAllText(autoSwapLockFile, string.Empty); File.SetLastWriteTimeUtc(autoSwapLockFile, DateTime.UtcNow.AddMinutes(-3)); Assert.False(PostDeploymentHelper.IsAutoSwapOngoing(), "Should not be any autoswap, since autoswap lock is acquired over 2 mintues ago."); File.WriteAllText(autoSwapLockFile, string.Empty); Assert.True(PostDeploymentHelper.IsAutoSwapOngoing(), "Autoswap is ongoing, since autoswap lock is acquired within 2 mintues"); } finally { System.Environment.SetEnvironmentVariable("HOME", homePath); System.Environment.SetEnvironmentVariable(Constants.WebSiteSwapSlotName, null); if (Directory.Exists(tempPath)) { Directory.Delete(tempPath, recursive: true); } } }
public async Task <FetchDeploymentRequestResult> FetchDeploy( DeploymentInfoBase deployInfo, bool asyncRequested, Uri requestUri, string targetBranch) { // 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 and zip deployment. if (!(_settings.IsScmEnabled() || deployInfo.AllowDeploymentWhileScmDisabled)) { return(FetchDeploymentRequestResult.ForbiddenScmDisabled); } // Else if this app is configured with a url in WEBSITE_USE_ZIP, then fail the deployment // since this is a RunFromZip site and the deployment has no chance of succeeding. else if (_settings.RunFromRemoteZip()) { return(FetchDeploymentRequestResult.ConflictRunFromRemoteZipConfigured); } // 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 isBackground = asyncRequested || deployInfo.IsContinuous; if (isBackground) { using (_tracer.Step("Start deployment in the background")) { var waitForTempDeploymentCreation = asyncRequested; var successfullyRequested = await PerformBackgroundDeployment( deployInfo, _environment, _settings, _tracer.TraceLevel, requestUri, waitForTempDeploymentCreation); return(successfullyRequested ? FetchDeploymentRequestResult.RunningAynschronously : FetchDeploymentRequestResult.ConflictDeploymentInProgress); } } _tracer.Trace("Attempting to fetch target branch {0}", targetBranch); try { return(await _deploymentLock.LockOperationAsync(async() => { if (PostDeploymentHelper.IsAutoSwapOngoing()) { return FetchDeploymentRequestResult.ConflictAutoSwapOngoing; } await PerformDeployment(deployInfo); return FetchDeploymentRequestResult.RanSynchronously; }, "Performing continuous deployment", TimeSpan.Zero)); } catch (LockOperationException) { if (deployInfo.AllowDeferredDeployment) { // 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(FetchDeploymentRequestResult.Pending); } else { return(FetchDeploymentRequestResult.ConflictDeploymentInProgress); } } }
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) { _tracer.Trace("No-op for deployment."); 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(); _tracer.Trace("Scm is not enabled, reject all requests."); 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; if (isBackground) { using (_tracer.Step("Start deployment in the background")) { var waitForTempDeploymentCreation = isAsync; await PerformBackgroundDeployment( deployInfo, _environment, _settings, _tracer.TraceLevel, UriHelper.GetRequestUri(context.Request), waitForTempDeploymentCreation); } // 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(UriHelper.GetRequestUri(context.Request), 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); try { await _deploymentLock.LockOperationAsync(async() => { if (PostDeploymentHelper.IsAutoSwapOngoing()) { context.Response.StatusCode = (int)HttpStatusCode.Conflict; context.Response.Write(Resources.Error_AutoSwapDeploymentOngoing); context.ApplicationInstance.CompleteRequest(); return; } await PerformDeployment(deployInfo); }, "Performing continuous deployment", TimeSpan.Zero); } catch (LockOperationException) { // 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 async Task <HttpResponseMessage> Deploy(string id = null) { JObject result = GetJsonContent(); // Just block here to read the json payload from the body using (_tracer.Step("DeploymentService.Deploy(id)")) { HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK); await _deploymentLock.LockHttpOperationAsync(async() => { try { if (PostDeploymentHelper.IsAutoSwapOngoing()) { throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.Conflict, Resources.Error_AutoSwapDeploymentOngoing)); } DeployResult deployResult; if (TryParseDeployResult(id, result, out deployResult)) { using (_tracer.Step("DeploymentService.Create(id)")) { CreateDeployment(deployResult, result.Value <string>("details")); // e.g if final url is "https://kudutry.scm.azurewebsites.net/api/deployments/ef52ec67fc9574e726955a9cbaf7bcba791e4e95/log" // deploymentUri should be "https://kudutry.scm.azurewebsites.net/api/deployments/ef52ec67fc9574e726955a9cbaf7bcba791e4e95" Uri deploymentUri = UriHelper.MakeRelative(UriHelper.GetBaseUri(Request), Request.RequestUri.AbsolutePath); deployResult.Url = deploymentUri; deployResult.LogUrl = UriHelper.MakeRelative(deploymentUri, "log"); response = Request.CreateResponse(HttpStatusCode.OK, ArmUtils.AddEnvelopeOnArmRequest(deployResult, Request)); return; } } bool clean = false; bool needFileUpdate = true; if (result != null) { clean = result.Value <bool>("clean"); JToken needFileUpdateToken; if (result.TryGetValue("needFileUpdate", out needFileUpdateToken)) { needFileUpdate = needFileUpdateToken.Value <bool>(); } } string username = null; AuthUtility.TryExtractBasicAuthUser(Request, out username); IRepository repository = _repositoryFactory.GetRepository(); if (repository == null) { throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, Resources.Error_RepositoryNotFound)); } ChangeSet changeSet = null; if (!String.IsNullOrEmpty(id)) { changeSet = repository.GetChangeSet(id); if (changeSet == null) { string message = String.Format(CultureInfo.CurrentCulture, Resources.Error_DeploymentNotFound, id); throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, message)); } } try { await _deploymentManager.DeployAsync(repository, changeSet, username, clean, needFileUpdate); } catch (DeploymentFailedException ex) { if (!ArmUtils.IsArmRequest(Request)) { throw; } // if requests comes thru ARM, we adjust the error code from 500 -> 400 throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex.ToString())); } // auto-swap if (PostDeploymentHelper.IsAutoSwapEnabled()) { if (changeSet == null) { var targetBranch = _settings.GetBranch(); changeSet = repository.GetChangeSet(targetBranch); } IDeploymentStatusFile statusFile = _status.Open(changeSet.Id); if (statusFile != null && statusFile.Status == DeployStatus.Success) { await PostDeploymentHelper.PerformAutoSwap(_environment.RequestId, _environment.SiteRestrictedJwt, new PostDeploymentTraceListener(_tracer, _deploymentManager.GetLogger(changeSet.Id))); } } } catch (FileNotFoundException ex) { throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, ex)); } }, "Performing deployment"); return(response); } }
public async Task Invoke( HttpContext context, ITracer tracer, IGitServer gitServer, IDictionary <string, IOperationLock> namedLocks, IDeploymentManager deploymentManager, IRepositoryFactory repositoryFactory, IEnvironment environment) { //Get the deployment lock from the locks dictionary var deploymentLock = namedLocks["deployment"]; using (tracer.Step("RpcService.ReceivePack")) { // Ensure that the target directory does not have a non-Git repository. IRepository repository = repositoryFactory.GetRepository(); if (repository != null && repository.RepositoryType != RepositoryType.Git) { context.Response.StatusCode = StatusCodes.Status400BadRequest; return; } try { await deploymentLock.LockOperationAsync(() => { context.Response.ContentType = "application/x-git-receive-pack-result"; if (PostDeploymentHelper.IsAutoSwapOngoing()) { context.Response.StatusCode = StatusCodes.Status409Conflict; var msg = Encoding.UTF8.GetBytes(Resources.Error_AutoSwapDeploymentOngoing); return(context.Response.Body.WriteAsync(msg, 0, msg.Length)); } string username = null; if (AuthUtility.TryExtractBasicAuthUser(context.Request, out username)) { gitServer.SetDeployer(username); } UpdateNoCacheForResponse(context.Response); // This temporary deployment is for ui purposes only, it will always be deleted via finally. ChangeSet tempChangeSet; using (deploymentManager.CreateTemporaryDeployment(Resources.ReceivingChanges, out tempChangeSet)) { // to pass to kudu.exe post receive hook System.Environment.SetEnvironmentVariable(Constants.RequestIdHeader, environment.RequestId); try { gitServer.Receive(context.Request.Body, context.Response.Body); } finally { System.Environment.SetEnvironmentVariable(Constants.RequestIdHeader, null); } } return(Task.CompletedTask); }, "Handling git receive pack", TimeSpan.Zero); } catch (LockOperationException ex) { context.Response.StatusCode = StatusCodes.Status409Conflict; var msg = Encoding.UTF8.GetBytes(ex.Message); await context.Response.Body.WriteAsync(msg, 0, msg.Length); } } }