Example #1
0
        private static string GetOutputPath(DeploymentInfoBase deploymentInfo, IEnvironment environment, IDeploymentSettingsManager perDeploymentSettings)
        {
            if (deploymentInfo?.Deployer == Constants.OneDeploy || deploymentInfo?.Deployer == Constants.ZipDeploy)
            {
                if (!string.IsNullOrWhiteSpace(deploymentInfo.TargetRootPath))
                {
                    return(deploymentInfo.TargetRootPath);
                }
            }

            string targetSubDirectoryRelativePath = perDeploymentSettings.GetTargetPath();

            if (String.IsNullOrWhiteSpace(targetSubDirectoryRelativePath))
            {
                targetSubDirectoryRelativePath = deploymentInfo?.TargetSubDirectoryRelativePath;
            }

            if (!String.IsNullOrWhiteSpace(targetSubDirectoryRelativePath))
            {
                targetSubDirectoryRelativePath = targetSubDirectoryRelativePath.Trim('\\', '/');
                return(Path.GetFullPath(Path.Combine(environment.WebRootPath, targetSubDirectoryRelativePath)));
            }

            return(environment.WebRootPath);
        }
Example #2
0
        // For continuous integration, we will only build/deploy if fetch new changes
        // The immediate goal is to address duplicated /deploy requests from Bitbucket (retry if taken > 20s)
        private bool ShouldDeploy(IRepository repository, DeploymentInfoBase deploymentInfo, string targetBranch)
        {
            if (deploymentInfo.IsContinuous)
            {
                ChangeSet changeSet = repository.GetChangeSet(targetBranch);
                return(!String.Equals(_status.ActiveDeploymentId, changeSet.Id, StringComparison.OrdinalIgnoreCase));
            }

            return(true);
        }
Example #3
0
        public async Task RestartMainSiteIfNeeded(ITracer tracer, ILogger logger, DeploymentInfoBase deploymentInfo)
        {
            // If post-deployment restart is disabled, do nothing.
            if (!_settings.RestartAppOnGitDeploy())
            {
                return;
            }

            // Proceed only if 'restart' is allowed for this deployment
            if (deploymentInfo != null && !deploymentInfo.RestartAllowed)
            {
                return;
            }

            if (deploymentInfo != null && !string.IsNullOrEmpty(deploymentInfo.DeploymentTrackingId))
            {
                // Send deployment status update to FE
                // FE will modify the operations table with the PostBuildRestartRequired status
                DeployStatusApiResult updateStatusObj = new DeployStatusApiResult(Constants.PostBuildRestartRequired, deploymentInfo.DeploymentTrackingId);
                bool isSuccess = await SendDeployStatusUpdate(updateStatusObj);

                if (isSuccess)
                {
                    // If operation is a success and PostBuildRestartRequired was posted successfully to the operations DB, then return
                    // Else fallthrough to RestartMainSiteAsync
                    return;
                }
            }

            if (deploymentInfo != null && deploymentInfo.Deployer == Constants.OneDeploy)
            {
                await PostDeploymentHelper.RestartMainSiteAsync(_environment.RequestId, new PostDeploymentTraceListener(tracer, logger));

                return;
            }

            if (_settings.RecylePreviewEnabled())
            {
                logger.Log("Triggering recycle (preview mode enabled).");
                tracer.Trace("Triggering recycle (preview mode enabled).");

                await PostDeploymentHelper.RestartMainSiteAsync(_environment.RequestId, new PostDeploymentTraceListener(tracer, logger));
            }
            else
            {
                logger.Log("Triggering recycle (preview mode disabled).");
                tracer.Trace("Triggering recycle (preview mode disabled).");

                DockerContainerRestartTrigger.RequestContainerRestart(_environment, RestartTriggerReason, tracer);
            }
        }
Example #4
0
        private static string GetOutputPath(DeploymentInfoBase deploymentInfo, IEnvironment environment, IDeploymentSettingsManager perDeploymentSettings)
        {
            string targetDirectoryPath = perDeploymentSettings.GetTargetPath();

            if (String.IsNullOrWhiteSpace(targetDirectoryPath))
            {
                targetDirectoryPath = deploymentInfo?.TargetDirectoryPath;
            }

            if (!String.IsNullOrWhiteSpace(targetDirectoryPath))
            {
                targetDirectoryPath = targetDirectoryPath.Trim('\\', '/');
                return(Path.GetFullPath(Path.Combine(environment.WebRootPath, targetDirectoryPath)));
            }

            return(environment.WebRootPath);
        }
Example #5
0
        // Touch watched file (web.config, web.xml, etc)
        private static void TryTouchWatchedFile(DeploymentContext context, DeploymentInfoBase deploymentInfo)
        {
            try
            {
                string watchedFileRelativePath = deploymentInfo?.WatchedFilePath;
                if (string.IsNullOrWhiteSpace(watchedFileRelativePath))
                {
                    watchedFileRelativePath = "web.config";
                }

                string watchedFileAbsolutePath = Path.Combine(context.OutputPath, watchedFileRelativePath);

                if (File.Exists(watchedFileAbsolutePath))
                {
                    File.SetLastWriteTimeUtc(watchedFileAbsolutePath, DateTime.UtcNow);
                }
            }
            catch (Exception ex)
            {
                context.Tracer.TraceError(ex);
            }
        }
Example #6
0
        public async Task RestartMainSiteIfNeeded(ITracer tracer, ILogger logger, DeploymentInfoBase deploymentInfo)
        {
            // If post-deployment restart is disabled, do nothing.
            if (!_settings.RestartAppOnGitDeploy())
            {
                return;
            }

            // Proceed only if 'restart' is allowed for this deployment
            if (deploymentInfo != null && !deploymentInfo.RestartAllowed)
            {
                return;
            }

            if (deploymentInfo != null && deploymentInfo.Deployer == Constants.OneDeploy)
            {
                await PostDeploymentHelper.RestartMainSiteAsync(_environment.RequestId, new PostDeploymentTraceListener(tracer, logger));

                return;
            }

            if (_settings.RecylePreviewEnabled())
            {
                logger.Log("Triggering recycle (preview mode enabled).");
                tracer.Trace("Triggering recycle (preview mode enabled).");

                await PostDeploymentHelper.RestartMainSiteAsync(_environment.RequestId, new PostDeploymentTraceListener(tracer, logger));
            }
            else
            {
                logger.Log("Triggering recycle (preview mode disabled).");
                tracer.Trace("Triggering recycle (preview mode disabled).");

                DockerContainerRestartTrigger.RequestContainerRestart(_environment, RestartTriggerReason, tracer);
            }
        }
Example #7
0
        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);
                }
            }
        }
Example #8
0
        // key goal is to create background tracer that is independent of request.
        public static async Task <bool> PerformBackgroundDeployment(
            DeploymentInfoBase deployInfo,
            IEnvironment environment,
            IDeploymentSettingsManager settings,
            TraceLevel traceLevel,
            Uri uri,
            bool waitForTempDeploymentCreation)
        {
            var tracer       = traceLevel <= TraceLevel.Off ? NullTracer.Instance : new CascadeTracer(new XmlTracer(environment.TracePath, traceLevel), new ETWTracer(environment.RequestId, "POST"));
            var traceFactory = new TracerFactory(() => tracer);

            var backgroundTrace = tracer.Step(XmlTracer.BackgroundTrace, new Dictionary <string, string>
            {
                { "url", uri.AbsolutePath },
                { "method", "POST" }
            });

            // For waiting on creation of temp deployment
            var tempDeploymentCreatedTcs = new TaskCompletionSource <object>();

            // For determining whether or not we failed to create the deployment due to lock contention.
            // Needed for deployments where deferred deployment is not allowed. Will be set to false if
            // lock contention occurs and AllowDeferredDeployment is false, otherwise true.
            var deploymentWillOccurTcs = new TaskCompletionSource <bool>();

            // This task will be let out of scope intentionally
            var deploymentTask = Task.Run(() =>
            {
                try
                {
                    // lock related
                    string lockPath           = Path.Combine(environment.SiteRootPath, Constants.LockPath);
                    string deploymentLockPath = Path.Combine(lockPath, Constants.DeploymentLockFile);
                    string statusLockPath     = Path.Combine(lockPath, Constants.StatusLockFile);
                    string hooksLockPath      = Path.Combine(lockPath, Constants.HooksLockFile);
                    var statusLock            = new LockFile(statusLockPath, traceFactory, traceLock: false);
                    var hooksLock             = new LockFile(hooksLockPath, traceFactory);
                    var deploymentLock        = new DeploymentLockFile(deploymentLockPath, traceFactory);

                    var analytics = new Analytics(settings, new ServerConfiguration(), traceFactory);
                    var deploymentStatusManager = new DeploymentStatusManager(environment, analytics, statusLock);
                    var siteBuilderFactory      = new SiteBuilderFactory(new BuildPropertyProvider(), environment);
                    var webHooksManager         = new WebHooksManager(tracer, environment, hooksLock);
                    var deploymentManager       = new DeploymentManager(siteBuilderFactory, environment, traceFactory, analytics, settings, deploymentStatusManager, deploymentLock, NullLogger.Instance, webHooksManager);
                    var fetchDeploymentManager  = new FetchDeploymentManager(settings, environment, tracer, deploymentLock, deploymentManager, deploymentStatusManager);

                    IDisposable tempDeployment = null;

                    try
                    {
                        // Perform deployment
                        deploymentLock.LockOperation(() =>
                        {
                            deploymentWillOccurTcs.TrySetResult(true);

                            ChangeSet tempChangeSet = null;
                            if (waitForTempDeploymentCreation)
                            {
                                // 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);

                                tempDeploymentCreatedTcs.TrySetResult(null);
                            }

                            fetchDeploymentManager.PerformDeployment(deployInfo, tempDeployment, tempChangeSet).Wait();
                        }, "Performing continuous deployment", TimeSpan.Zero);
                    }
                    catch (LockOperationException)
                    {
                        if (tempDeployment != null)
                        {
                            OperationManager.SafeExecute(() => tempDeployment.Dispose());
                        }

                        if (deployInfo.AllowDeferredDeployment)
                        {
                            deploymentWillOccurTcs.TrySetResult(true);

                            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(fetchDeploymentManager._markerFilePath, DateTime.UtcNow);
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    tracer.TraceError(ex);
                }
                finally
                {
                    // Will no-op if already set
                    deploymentWillOccurTcs.TrySetResult(false);
                    backgroundTrace.Dispose();
                }
            });

#pragma warning disable 4014
            // Run on BG task (Task.Run) to avoid ASP.NET Request thread terminated with request completion and
            // it doesn't get chance to clean up the pending marker.
            Task.Run(() => PostDeploymentHelper.TrackPendingOperation(deploymentTask, TimeSpan.Zero));
#pragma warning restore 4014

            // When the frontend/ARM calls /deploy with isAsync=true, it starts polling
            // the deployment status immediately, so it's important that the temp deployment
            // is created before we return.
            if (waitForTempDeploymentCreation)
            {
                // deploymentTask may return without creating the temp deployment (lock contention,
                // other exception), in which case just continue.
                await Task.WhenAny(tempDeploymentCreatedTcs.Task, deploymentTask);
            }

            // If deferred deployment is not permitted, we need to know whether or not the deployment was
            // successfully requested. Otherwise, to preserve existing behavior, we assume it was.
            if (!deployInfo.AllowDeferredDeployment)
            {
                return(await deploymentWillOccurTcs.Task);
            }
            else
            {
                return(true);
            }
        }
Example #9
0
        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)));
                }
            }
        }
Example #10
0
        private static void TouchWatchedFileIfNeeded(IDeploymentSettingsManager settings, DeploymentInfoBase deploymentInfo, DeploymentContext context)
        {
            if (deploymentInfo != null && !deploymentInfo.WatchedFileEnabled)
            {
                return;
            }

            if (!settings.RunFromZip() && settings.TouchWatchedFileAfterDeployment())
            {
                TryTouchWatchedFile(context, deploymentInfo);
            }
        }
Example #11
0
        /// <summary>
        /// Builds and deploys a particular changeset. Puts all build artifacts in a deployments/{id}
        /// </summary>
        private async Task Build(
            ChangeSet changeSet,
            ITracer tracer,
            IDisposable deployStep,
            IRepository repository,
            DeploymentInfoBase deploymentInfo,
            DeploymentAnalytics deploymentAnalytics,
            bool fullBuildByDefault)
        {
            if (changeSet == null || String.IsNullOrEmpty(changeSet.Id))
            {
                throw new ArgumentException("The changeSet.Id parameter is null or empty", "changeSet.Id");
            }

            ILogger logger = null;
            IDeploymentStatusFile currentStatus = null;
            string buildTempPath = null;
            string id            = changeSet.Id;

            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;

                // Add in per-deploy default settings values based on the details of this deployment
                var perDeploymentDefaults = new Dictionary <string, string> {
                    { SettingsKeys.DoBuildDuringDeployment, fullBuildByDefault.ToString() }
                };
                var settingsProviders = _settings.SettingsProviders.Concat(
                    new[] { new BasicSettingsProvider(perDeploymentDefaults, SettingsProvidersPriority.PerDeploymentDefault) });

                var perDeploymentSettings = DeploymentSettingsManager.BuildPerDeploymentSettingsManager(repository.RepositoryPath, settingsProviders);

                string delayMaxInStr = perDeploymentSettings.GetValue(SettingsKeys.MaxRandomDelayInSec);
                if (!String.IsNullOrEmpty(delayMaxInStr))
                {
                    int maxDelay;
                    if (!Int32.TryParse(delayMaxInStr, out maxDelay) || maxDelay < 0)
                    {
                        tracer.Trace("Invalid {0} value, expect a positive integer, received {1}", SettingsKeys.MaxRandomDelayInSec, delayMaxInStr);
                    }
                    else
                    {
                        tracer.Trace("{0} is set to {1}s", SettingsKeys.MaxRandomDelayInSec, maxDelay);
                        int gap = _random.Next(maxDelay);
                        using (tracer.Step("Randomization applied to {0}, Start sleeping for {1}s", maxDelay, gap))
                        {
                            logger.Log(Resources.Log_DelayingBeforeDeployment, gap);
                            await Task.Delay(TimeSpan.FromSeconds(gap));
                        }
                    }
                }

                try
                {
                    using (tracer.Step("Determining deployment builder"))
                    {
                        builder = _builderFactory.CreateBuilder(tracer, innerLogger, perDeploymentSettings, repository, deploymentInfo);
                        deploymentAnalytics.ProjectType = builder.ProjectType;
                        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);

                    innerLogger.Log(ex);

                    MarkStatusComplete(currentStatus, success: false);

                    FailDeployment(tracer, deployStep, deploymentAnalytics, ex);

                    return;
                }

                // Create a directory for the script output temporary artifacts
                // Use tick count (in hex) instead of guid to keep the path for getting to long
                buildTempPath = Path.Combine(_environment.TempPath, DateTime.UtcNow.Ticks.ToString("x"));
                FileSystemHelpers.EnsureDirectory(buildTempPath);

                var context = new DeploymentContext
                {
                    NextManifestFilePath     = GetDeploymentManifestPath(id),
                    PreviousManifestFilePath = GetActiveDeploymentManifestPath(),
                    IgnoreManifest           = deploymentInfo != null && deploymentInfo.CleanupTargetDirectory,
                    // Ignoring the manifest will cause kudusync to delete sub-directories / files
                    // in the destination directory that are not present in the source directory,
                    // without checking the manifest to see if the file was copied over to the destination
                    // during a previous kudusync operation. This effectively performs a clean deployment
                    // from the source to the destination directory.
                    Tracer        = tracer,
                    Logger        = logger,
                    GlobalLogger  = _globalLogger,
                    OutputPath    = GetOutputPath(deploymentInfo, _environment, perDeploymentSettings),
                    BuildTempPath = buildTempPath,
                    CommitId      = id,
                    Message       = changeSet.Message
                };

                if (context.PreviousManifestFilePath == null)
                {
                    // this file (/site/firstDeploymentManifest) capture the last active deployment when disconnecting SCM
                    context.PreviousManifestFilePath = Path.Combine(_environment.SiteRootPath, Constants.FirstDeploymentManifestFileName);
                    if (!FileSystemHelpers.FileExists(context.PreviousManifestFilePath))
                    {
                        // 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);
                    }
                }

                PreDeployment(tracer);

                using (tracer.Step("Building"))
                {
                    try
                    {
                        await builder.Build(context);

                        builder.PostBuild(context);
                        await RestartMainSiteIfNeeded(tracer, logger, deploymentInfo);

                        await PostDeploymentHelper.SyncFunctionsTriggers(_environment.RequestId, new PostDeploymentTraceListener(tracer, logger), deploymentInfo?.SyncFunctionsTriggersPath);

                        TouchWatchedFileIfNeeded(_settings, deploymentInfo, context);

                        FinishDeployment(id, deployStep);

                        deploymentAnalytics.VsProjectId = TryGetVsProjectId(context);
                        deploymentAnalytics.Result      = DeployStatus.Success.ToString();
                    }
                    catch (Exception ex)
                    {
                        MarkStatusComplete(currentStatus, success: false);

                        FailDeployment(tracer, deployStep, deploymentAnalytics, ex);

                        return;
                    }
                }
            }
            catch (Exception ex)
            {
                FailDeployment(tracer, deployStep, deploymentAnalytics, ex);
            }
            finally
            {
                // Clean the temp folder up
                CleanBuild(tracer, buildTempPath);
            }
        }
Example #12
0
        public async Task DeployAsync(
            IRepository repository,
            ChangeSet changeSet,
            string deployer,
            bool clean,
            DeploymentInfoBase deploymentInfo = null,
            bool needFileUpdate     = true,
            bool fullBuildByDefault = true)
        {
            using (var deploymentAnalytics = new DeploymentAnalytics(_analytics, _settings))
            {
                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);

                    if (changeSet == null)
                    {
                        throw new InvalidOperationException(String.Format("The current deployment branch is '{0}', but nothing has been pushed to it", targetBranch));
                    }
                }

                string id = changeSet.Id;
                IDeploymentStatusFile statusFile = null;
                try
                {
                    deployStep = tracer.Step($"DeploymentManager.Deploy(id:{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);

                    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 specific changeset or branch
                                repository.Update(targetBranch ?? id);
                            }
                        }
                    }

                    if (_settings.ShouldUpdateSubmodules())
                    {
                        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(changeSet, tracer, deployStep, repository, deploymentInfo, deploymentAnalytics, fullBuildByDefault);
                }
                catch (Exception ex)
                {
                    exception = ex;

                    if (innerLogger != null)
                    {
                        innerLogger.Log(ex);
                    }

                    if (statusFile != null)
                    {
                        MarkStatusComplete(statusFile, success: false);
                    }

                    tracer.TraceError(ex);

                    deploymentAnalytics.Error = ex.ToString();

                    if (deployStep != null)
                    {
                        deployStep.Dispose();
                    }
                }

                // Remove leftover AppOffline file
                PostDeploymentHelper.RemoveAppOfflineIfLeft(_environment, null, tracer);

                // 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);
                }

                if (statusFile != null && statusFile.Status == DeployStatus.Success && _settings.RunFromLocalZip())
                {
                    var zipDeploymentInfo = deploymentInfo as ArtifactDeploymentInfo;
                    if (zipDeploymentInfo != null)
                    {
                        await PostDeploymentHelper.UpdateSiteVersion(zipDeploymentInfo, _environment, tracer);
                    }
                }
            }
        }