/// <summary> /// Ensure the deployment is in a valid state. /// </summary> private IDeploymentStatusFile VerifyDeployment(string id, bool isDeploying) { IDeploymentStatusFile statusFile = _status.Open(id); if (statusFile == null) { return(null); } if (statusFile.Complete) { return(statusFile); } // There's an incomplete deployment, yet nothing is going on, mark this deployment as failed // since it probably means something died if (!isDeploying) { ILogger logger = GetLogger(id); logger.LogUnexpectedError(); statusFile.MarkFailed(); } return(statusFile); }
private void MarkStatusComplete(IDeploymentStatusFile status, bool success) { if (success) { status.MarkSuccess(); } else { status.MarkFailed(); } // Cleaup old deployments PurgeAndGetDeployments(); }
private void MarkStatusComplete(IDeploymentStatusFile status, bool success) { if (success) { status.MarkSuccess(); } else { status.MarkFailed(); } // Cleanup old deployments PurgeAndGetDeployments(); // Report deployment completion DeploymentCompletedInfo.Persist(_environment.RequestId, status); }
private void MarkStatusComplete(IDeploymentStatusFile status, bool success) { if (success) { // Mitigation when status file is busy, // and update fails Thread.Sleep(200); status.MarkSuccess(); } else { status.MarkFailed(); } // Cleaup old deployments PurgeAndGetDeployments(); }
public async Task PerformDeployment(DeploymentInfoBase deploymentInfo, IDisposable tempDeployment = null, ChangeSet tempChangeSet = null) { DateTime currentMarkerFileUTC; DateTime nextMarkerFileUTC = FileSystemHelpers.GetLastWriteTimeUtc(_markerFilePath); ChangeSet lastChange = null; do { // save the current marker currentMarkerFileUTC = nextMarkerFileUTC; string targetBranch = _settings.GetBranch(); using (_tracer.Step("Performing fetch based deployment")) { // 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 = tempDeployment ?? _deploymentManager.CreateTemporaryDeployment( Resources.ReceivingChanges, out tempChangeSet, deploymentInfo.TargetChangeset, deploymentInfo.Deployer); ILogger innerLogger = null; DeployStatusApiResult updateStatusObj = null; try { ILogger logger = _deploymentManager.GetLogger(tempChangeSet.Id); // Fetch changes from the repository innerLogger = logger.Log(Resources.FetchingChanges); IRepository repository = deploymentInfo.GetRepository(); try { await deploymentInfo.Fetch(repository, deploymentInfo, targetBranch, innerLogger, _tracer); } catch (BranchNotFoundException) { // mark no deployment is needed deploymentInfo.TargetChangeset = null; } // set to null as Deploy() below takes over logging innerLogger = null; // The branch or commit id to deploy string deployBranch = !String.IsNullOrEmpty(deploymentInfo.CommitId) ? deploymentInfo.CommitId : targetBranch; try { _tracer.Trace($"Before sending {Constants.BuildRequestReceived} status to /api/updatedeploystatus"); if (PostDeploymentHelper.IsAzureEnvironment()) { if (deploymentInfo != null && !string.IsNullOrEmpty(deploymentInfo.DeploymentTrackingId)) { // Only send an updatedeploystatus request if DeploymentTrackingId is non null // This signifies the client has opted in for these deployment updates for this deploy request updateStatusObj = new DeployStatusApiResult(Constants.BuildRequestReceived, deploymentInfo.DeploymentTrackingId); await _deploymentManager.SendDeployStatusUpdate(updateStatusObj); } } } catch (Exception e) { _tracer.TraceError($"Exception while sending {Constants.BuildRequestReceived} status to /api/updatedeploystatus. " + $"Entry in the operations table for the deployment status may not have been created. {e}"); } // In case the commit or perhaps fetch do no-op. if (deploymentInfo.TargetChangeset != null && ShouldDeploy(repository, deploymentInfo, deployBranch)) { // Perform the actual deployment var changeSet = repository.GetChangeSet(deployBranch); if (changeSet == null && !String.IsNullOrEmpty(deploymentInfo.CommitId)) { throw new InvalidOperationException(String.Format("Invalid revision '{0}'!", deploymentInfo.CommitId)); } lastChange = changeSet; // Here, we don't need to update the working files, since we know Fetch left them in the correct state // unless for GenericHandler where specific commitId is specified bool deploySpecificCommitId = !String.IsNullOrEmpty(deploymentInfo.CommitId); if (updateStatusObj != null) { updateStatusObj.DeploymentStatus = Constants.BuildInProgress; await _deploymentManager.SendDeployStatusUpdate(updateStatusObj); } await _deploymentManager.DeployAsync( repository, changeSet, deploymentInfo.Deployer, clean : false, deploymentInfo : deploymentInfo, needFileUpdate : deploySpecificCommitId, fullBuildByDefault : deploymentInfo.DoFullBuildByDefault); if (updateStatusObj != null && !deploymentInfo.RestartAllowed) { // If restart is disallowed, send BuildSuccessful here as PostBuildRestartRequired was not sent // during the DeployAsync flow. updateStatusObj.DeploymentStatus = Constants.BuildSuccessful; await _deploymentManager.SendDeployStatusUpdate(updateStatusObj); } } } catch (Exception ex) { if (innerLogger != null) { innerLogger.Log(ex); } // In case the commit or perhaps fetch do no-op. if (deploymentInfo.TargetChangeset != null) { IDeploymentStatusFile statusFile = _status.Open(deploymentInfo.TargetChangeset.Id); if (statusFile != null) { statusFile.MarkFailed(); } } try { if (updateStatusObj != null) { // Set deployment status as failure if exception is thrown updateStatusObj.DeploymentStatus = Constants.BuildFailed; await _deploymentManager.SendDeployStatusUpdate(updateStatusObj); } } catch { // no-op } throw; } // only clean up temp deployment if successful tempDeployment.Dispose(); } // check marker file and, if changed (meaning new /deploy request), redeploy. nextMarkerFileUTC = FileSystemHelpers.GetLastWriteTimeUtc(_markerFilePath); } while (deploymentInfo.IsReusable && currentMarkerFileUTC != nextMarkerFileUTC); if (lastChange != null && PostDeploymentHelper.IsAutoSwapEnabled()) { IDeploymentStatusFile statusFile = _status.Open(lastChange.Id); if (statusFile.Status == DeployStatus.Success) { // if last change is not null and finish successfully, mean there was at least one deployment happened // since deployment is now done, trigger swap if enabled await PostDeploymentHelper.PerformAutoSwap(_environment.RequestId, new PostDeploymentTraceListener(_tracer, _deploymentManager.GetLogger(lastChange.Id))); } } }
private void MarkStatusComplete(IDeploymentStatusFile status, bool success) { if (success) { status.MarkSuccess(); } else { status.MarkFailed(); } // Cleaup old deployments PurgeAndGetDeployments(); }
public async Task PerformDeployment(DeploymentInfo deploymentInfo, IDisposable tempDeployment = null, ChangeSet tempChangeSet = null) { DateTime currentMarkerFileUTC; DateTime nextMarkerFileUTC = FileSystemHelpers.GetLastWriteTimeUtc(_markerFilePath); ChangeSet lastChange = null; do { // save the current marker currentMarkerFileUTC = nextMarkerFileUTC; string targetBranch = _settings.GetBranch(); using (_tracer.Step("Performing fetch based deployment")) { // 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 = tempDeployment ?? _deploymentManager.CreateTemporaryDeployment( Resources.ReceivingChanges, out tempChangeSet, deploymentInfo.TargetChangeset, deploymentInfo.Deployer); ILogger innerLogger = null; try { ILogger logger = _deploymentManager.GetLogger(tempChangeSet.Id); // Fetch changes from the repository innerLogger = logger.Log(Resources.FetchingChanges); IRepository repository = _repositoryFactory.EnsureRepository(deploymentInfo.RepositoryType); try { await deploymentInfo.Handler.Fetch(repository, deploymentInfo, targetBranch, innerLogger, _tracer); } catch (BranchNotFoundException) { // mark no deployment is needed deploymentInfo.TargetChangeset = null; } // set to null as Deploy() below takes over logging innerLogger = null; // The branch or commit id to deploy string deployBranch = !String.IsNullOrEmpty(deploymentInfo.CommitId) ? deploymentInfo.CommitId : targetBranch; // In case the commit or perhaps fetch do no-op. if (deploymentInfo.TargetChangeset != null && ShouldDeploy(repository, deploymentInfo, deployBranch)) { // Perform the actual deployment var changeSet = repository.GetChangeSet(deployBranch); if (changeSet == null && !String.IsNullOrEmpty(deploymentInfo.CommitId)) { throw new InvalidOperationException(String.Format("Invalid revision '{0}'!", deploymentInfo.CommitId)); } lastChange = changeSet; // Here, we don't need to update the working files, since we know Fetch left them in the correct state // unless for GenericHandler where specific commitId is specified bool deploySpecificCommitId = !String.IsNullOrEmpty(deploymentInfo.CommitId); await _deploymentManager.DeployAsync(repository, changeSet, deploymentInfo.Deployer, clean : false, needFileUpdate : deploySpecificCommitId); } } catch (Exception ex) { if (innerLogger != null) { innerLogger.Log(ex); } // In case the commit or perhaps fetch do no-op. if (deploymentInfo.TargetChangeset != null) { IDeploymentStatusFile statusFile = _status.Open(deploymentInfo.TargetChangeset.Id); if (statusFile != null) { statusFile.MarkFailed(); } } throw; } // only clean up temp deployment if successful tempDeployment.Dispose(); } // check marker file and, if changed (meaning new /deploy request), redeploy. nextMarkerFileUTC = FileSystemHelpers.GetLastWriteTimeUtc(_markerFilePath); } while (deploymentInfo.IsReusable && currentMarkerFileUTC != nextMarkerFileUTC); if (lastChange != null && PostDeploymentHelper.IsAutoSwapEnabled()) { IDeploymentStatusFile statusFile = _status.Open(lastChange.Id); if (statusFile.Status == DeployStatus.Success) { // if last change is not null and finish successfully, mean there was at least one deployoment happened // since deployment is now done, trigger swap if enabled await PostDeploymentHelper.PerformAutoSwap(_environment.RequestId, _environment.SiteRestrictedJwt, new PostDeploymentTraceListener(_tracer, _deploymentManager.GetLogger(lastChange.Id))); } } }
public async Task PerformDeployment(DeploymentInfo deploymentInfo) { DateTime currentMarkerFileUTC; DateTime nextMarkerFileUTC = FileSystemHelpers.GetLastWriteTimeUtc(_markerFilePath); do { // save the current marker currentMarkerFileUTC = nextMarkerFileUTC; string targetBranch = _settings.GetBranch(); using (_tracer.Step("Performing fetch based deployment")) { // 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 ChangeSet tempChangeSet; IDisposable tempDeployment = _deploymentManager.CreateTemporaryDeployment( Resources.ReceivingChanges, out tempChangeSet, deploymentInfo.TargetChangeset, deploymentInfo.Deployer); ILogger innerLogger = null; try { ILogger logger = _deploymentManager.GetLogger(tempChangeSet.Id); // Fetch changes from the repository innerLogger = logger.Log(Resources.FetchingChanges); IRepository repository = _repositoryFactory.EnsureRepository(deploymentInfo.RepositoryType); try { await deploymentInfo.Handler.Fetch(repository, deploymentInfo, targetBranch, innerLogger); } catch (BranchNotFoundException) { // mark no deployment is needed deploymentInfo.TargetChangeset = null; } // set to null as Deploy() below takes over logging innerLogger = null; // In case the commit or perhaps fetch do no-op. if (deploymentInfo.TargetChangeset != null && ShouldDeploy(repository, deploymentInfo, targetBranch)) { // Perform the actual deployment var changeSet = repository.GetChangeSet(targetBranch); // Here, we don't need to update the working files, since we know Fetch left them in the correct state await _deploymentManager.DeployAsync(repository, changeSet, deploymentInfo.Deployer, clean : false, needFileUpdate : false); } } catch (Exception ex) { if (innerLogger != null) { innerLogger.Log(ex); } // In case the commit or perhaps fetch do no-op. if (deploymentInfo.TargetChangeset != null) { IDeploymentStatusFile statusFile = _status.Open(deploymentInfo.TargetChangeset.Id); if (statusFile != null) { statusFile.MarkFailed(); } } throw; } // only clean up temp deployment if successful tempDeployment.Dispose(); } // check marker file and, if changed (meaning new /deploy request), redeploy. nextMarkerFileUTC = FileSystemHelpers.GetLastWriteTimeUtc(_markerFilePath); } while (deploymentInfo.IsReusable && currentMarkerFileUTC != nextMarkerFileUTC); }
/// <summary> /// Builds and deploys a particular changeset. Puts all build artifacts in a deployments/{id} /// </summary> private async Task Build(string id, ITracer tracer, IDisposable deployStep, IFileFinder fileFinder) { if (String.IsNullOrEmpty(id)) { throw new ArgumentException("The id parameter is null or empty", "id"); } ILogger logger = null; IDeploymentStatusFile currentStatus = null; try { logger = GetLogger(id); ILogger innerLogger = logger.Log(Resources.Log_PreparingDeployment, TrimId(id)); currentStatus = _status.Open(id); currentStatus.Complete = false; currentStatus.StartTime = DateTime.UtcNow; currentStatus.Status = DeployStatus.Building; currentStatus.StatusText = String.Format(CultureInfo.CurrentCulture, Resources.Status_BuildingAndDeploying, id); currentStatus.Save(); ISiteBuilder builder = null; string repositoryRoot = _environment.RepositoryPath; var perDeploymentSettings = DeploymentSettingsManager.BuildPerDeploymentSettingsManager(repositoryRoot, _settings); try { using (tracer.Step("Determining deployment builder")) { builder = _builderFactory.CreateBuilder(tracer, innerLogger, perDeploymentSettings, fileFinder); tracer.Trace("Builder is {0}", builder.GetType().Name); } } catch (Exception ex) { // If we get a TargetInvocationException, use the inner exception instead to avoid // useless 'Exception has been thrown by the target of an invocation' messages var targetInvocationException = ex as System.Reflection.TargetInvocationException; if (targetInvocationException != null) { ex = targetInvocationException.InnerException; } _globalLogger.Log(ex); tracer.TraceError(ex); innerLogger.Log(ex); currentStatus.MarkFailed(); deployStep.Dispose(); return; } var context = new DeploymentContext { NextManifestFilePath = GetDeploymentManifestPath(id), PreviousManifestFilePath = GetActiveDeploymentManifestPath(), Tracer = tracer, Logger = logger, GlobalLogger = _globalLogger, OutputPath = GetOutputPath(_environment, perDeploymentSettings), }; if (context.PreviousManifestFilePath == null) { // In the first deployment we want the wwwroot directory to be cleaned, we do that using a manifest file // That has the expected content of a clean deployment (only one file: hostingstart.html) // This will result in KuduSync cleaning this file. context.PreviousManifestFilePath = Path.Combine(_environment.ScriptPath, Constants.FirstDeploymentManifestFileName); } using (tracer.Step("Building")) { try { await builder.Build(context); TryTouchWebConfig(context); // Run post deployment steps FinishDeployment(id, deployStep); } catch (Exception ex) { tracer.TraceError(ex); currentStatus.MarkFailed(); // End the deploy step deployStep.Dispose(); return; } } } catch (Exception ex) { tracer.TraceError(ex); deployStep.Dispose(); } }
public async Task DeployAsync(IRepository repository, ChangeSet changeSet, string deployer, bool clean, bool needFileUpdate) { Exception exception = null; ITracer tracer = _traceFactory.GetTracer(); IDisposable deployStep = null; ILogger innerLogger = null; string targetBranch = null; // If we don't get a changeset, find out what branch we should be deploying and get the commit ID from it if (changeSet == null) { targetBranch = _settings.GetBranch(); changeSet = repository.GetChangeSet(targetBranch); } string id = changeSet.Id; IDeploymentStatusFile statusFile = null; try { deployStep = tracer.Step("DeploymentManager.Deploy(id)"); // Remove the old log file for this deployment id string logPath = GetLogPath(id); FileSystemHelpers.DeleteFileSafe(logPath); statusFile = GetOrCreateStatusFile(changeSet, tracer, deployer); statusFile.MarkPending(); ILogger logger = GetLogger(changeSet.Id); repository.ClearLock(); if (needFileUpdate) { using (tracer.Step("Updating to specific changeset")) { innerLogger = logger.Log(Resources.Log_UpdatingBranch, targetBranch ?? id); using (var writer = new ProgressWriter()) { // Update to the the specific changeset repository.Update(id); } } } using (tracer.Step("Updating submodules")) { innerLogger = logger.Log(Resources.Log_UpdatingSubmodules); repository.UpdateSubmodules(); } if (clean) { tracer.Trace("Cleaning {0} repository", repository.RepositoryType); innerLogger = logger.Log(Resources.Log_CleaningRepository, repository.RepositoryType); repository.Clean(); } // set to null as Build() below takes over logging innerLogger = null; // Perform the build deployment of this changeset await Build(id, tracer, deployStep, repository); } catch (Exception ex) { exception = ex; if (innerLogger != null) { innerLogger.Log(ex); } if (statusFile != null) { statusFile.MarkFailed(); } tracer.TraceError(ex); if (deployStep != null) { deployStep.Dispose(); } } // Reload status file with latest updates statusFile = _status.Open(id); if (statusFile != null) { await _hooksManager.PublishEventAsync(HookEventTypes.PostDeployment, statusFile); } if (exception != null) { throw new DeploymentFailedException(exception); } }