private void MarkStatusComplete(IDeploymentStatusFile status, bool success)
        {
            if (success)
            {
                status.MarkSuccess();
            }
            else
            {
                status.MarkFailed();
            }

            // Cleaup old deployments
            PurgeAndGetDeployments();
        }
Example #2
0
        public async Task DeployAsync(IRepository repository, ChangeSet changeSet, string deployer, bool clean, bool needFileUpdate)
        {
            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)");

                    // 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(id, tracer, deployStep, repository, deploymentAnalytics);
                }
                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();
                    }
                }

                // 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);
                }
            }
        }
Example #3
0
        /// <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, DeploymentAnalytics deploymentAnalytics)
        {
            if (String.IsNullOrEmpty(id))
            {
                throw new ArgumentException("The id parameter is null or empty", "id");
            }

            ILogger logger = null;
            IDeploymentStatusFile currentStatus = null;
            string buildTempPath = 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);
                        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
                buildTempPath = Path.Combine(_environment.TempPath, Guid.NewGuid().ToString());
                FileSystemHelpers.EnsureDirectory(buildTempPath);

                var context = new DeploymentContext
                {
                    NextManifestFilePath     = GetDeploymentManifestPath(id),
                    PreviousManifestFilePath = GetActiveDeploymentManifestPath(),
                    Tracer        = tracer,
                    Logger        = logger,
                    GlobalLogger  = _globalLogger,
                    OutputPath    = GetOutputPath(_environment, perDeploymentSettings),
                    BuildTempPath = buildTempPath,
                    CommitId      = id
                };

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

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

                        TryTouchWebConfig(context);

                        // Run post deployment steps
                        FinishDeployment(id, deployStep);

                        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 #4
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, IFileFinder fileFinder, DeploymentAnalytics deploymentAnalytics)
        {
            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;

                string repositoryRoot        = _environment.RepositoryPath;
                var    perDeploymentSettings = DeploymentSettingsManager.BuildPerDeploymentSettingsManager(repositoryRoot, _settings);

                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, fileFinder);
                        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(),
                    Tracer        = tracer,
                    Logger        = logger,
                    GlobalLogger  = _globalLogger,
                    OutputPath    = GetOutputPath(_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 PostDeploymentHelper.SyncFunctionsTriggers(_environment.RequestId, _environment.SiteRestrictedJwt, new PostDeploymentTraceListener(tracer, logger));

                        if (_settings.TouchWebConfigAfterDeployment())
                        {
                            TryTouchWebConfig(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 #5
0
        internal async Task <ChangeSet> Sync(DropboxInfo dropboxInfo, string branch, IRepository repository, ITracer tracer)
        {
            DropboxDeployInfo deployInfo = dropboxInfo.DeployInfo;

            // use incoming tracer since it is background work
            _tracer = tracer;

            ResetStats();

            // for Dropbox OAuth V2, the delta is collected and applied by SCM
            // simply set OldCursoras current.
            if (dropboxInfo.OAuthVersion == 2)
            {
                deployInfo.OldCursor = _settings.GetValue(CursorKey);
            }
            else if (_settings.GetValue(CursorKey) != deployInfo.OldCursor)
            {
                throw new InvalidOperationException(Resources.Error_MismatchDropboxCursor);
            }

            // initial sync, remove default content
            // for simplicity, we do it blindly whether or not in-place
            // given the end result is the same
            if (String.IsNullOrEmpty(deployInfo.OldCursor) && DeploymentHelper.IsDefaultWebRootContent(_environment.WebRootPath))
            {
                string hoststarthtml = Path.Combine(_environment.WebRootPath, Constants.HostingStartHtml);
                FileSystemHelpers.DeleteFileSafe(hoststarthtml);
            }

            if (!repository.IsEmpty())
            {
                // git checkout --force <branch>
                repository.Update(branch);
            }

            ChangeSet changeSet = null;
            string    message   = null;

            try
            {
                using (_tracer.Step("Sync with Dropbox"))
                {
                    if (dropboxInfo.OAuthVersion == 2)
                    {
                        // Fetch the deltas
                        await UpdateDropboxDeployInfo(deployInfo);
                    }

                    // Sync dropbox => repository directory
                    await ApplyChanges(dropboxInfo, useOAuth20 : dropboxInfo.OAuthVersion == 2);
                }

                message = String.Format(CultureInfo.CurrentCulture,
                                        Resources.Dropbox_Synchronized,
                                        deployInfo.Deltas.Count);
            }
            catch (Exception)
            {
                message = String.Format(CultureInfo.CurrentCulture,
                                        Resources.Dropbox_SynchronizedWithFailure,
                                        _successCount,
                                        deployInfo.Deltas.Count,
                                        _failedCount);

                throw;
            }
            finally
            {
                Logger.Log(message);

                Logger.Log(String.Format("{0} downloaded files, {1} successful retries.", _fileCount, _retriedCount));

                IDeploymentStatusFile statusFile = _status.Open(dropboxInfo.TargetChangeset.Id);
                statusFile.UpdateMessage(message);
                statusFile.UpdateProgress(String.Format(CultureInfo.CurrentCulture, Resources.Dropbox_Committing, _successCount));

                // Commit anyway even partial change
                if (repository.Commit(message, deployInfo.UserName, deployInfo.Email ?? deployInfo.UserName))
                {
                    changeSet = repository.GetChangeSet("HEAD");
                }
            }

            // Save new dropboc cursor
            LogInfo("Update dropbox cursor");
            _settings.SetValue(CursorKey, deployInfo.NewCursor);

            return(changeSet);
        }
        private async Task <ChangesResult> GetChanges(string changeset, string accessToken, string requestUri, string cursor)
        {
            string rootUri = GetOneDriveRootUri(requestUri);

            requestUri = string.Format(CultureInfo.InvariantCulture, "{0}/{1}", await GetItemUri(accessToken, requestUri, rootUri), "view.changes");

            ChangesResult         result     = new ChangesResult();
            IDeploymentStatusFile statusFile = _status.Open(changeset);
            var loop = 0;

            using (var client = CreateHttpClient(accessToken))
            {
                var next = cursor;
                var ids  = new Dictionary <string, OneDriveModel.ItemInfo>();
                Dictionary <string, object> changes = null;
                // CORE TODO I can't quite tell what's going on here so failing for now.
                //var serializer = new JavaScriptSerializer();
                do
                {
                    var uri = requestUri;
                    if (!string.IsNullOrWhiteSpace(next))
                    {
                        uri = string.Format(CultureInfo.InvariantCulture, "{0}?token={1}", requestUri, next);
                    }

                    ++loop;
                    statusFile.UpdateProgress(string.Format(CultureInfo.CurrentCulture, "Getting delta changes batch #{0}", loop));
                    using (_tracer.Step("Getting delta changes batch #{0}", loop))
                    {
                        using (var response = await client.GetAsync(uri))
                        {
                            changes = await ProcessResponse <Dictionary <string, object> >("GetChanges", response);
                        }
                    }

                    if (changes.ContainsKey("@changes.resync"))
                    {
                        if (string.IsNullOrEmpty(next))
                        {
                            throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resources.OneDriveUnableToSync, changes["@changes.resync"]));
                        }

                        // resync
                        loop = 0;
                        next = null;
                        changes["@changes.hasMoreChanges"] = true;
                        result = new ChangesResult();
                        continue;
                    }

                    // CORE TODO I can't quite tell what's going on here so failing for now.
                    //var items = serializer.ConvertToType<OneDriveModel.OneDriveItemCollection>(changes);
                    OneDriveModel.OneDriveItemCollection items = null;

                    // changes
                    if (items != null && items.value != null && items.value.Count > 0)
                    {
                        var subResults = GetChanges(items, ids, rootUri);
                        result.DeletionChanges.AddRange(subResults.DeletionChanges);
                        result.DirectoryChanges.AddRange(subResults.DirectoryChanges);
                        result.FileChanges.AddRange(subResults.FileChanges);
                    }

                    // set next token
                    next = (string)changes["@changes.token"];
                } while ((bool)changes["@changes.hasMoreChanges"]);

                result.Cursor = next;
                return(result);
            }
        }
Example #7
0
        public async Task <IActionResult> Deploy(string id = null)
        {
            JObject jsonContent = GetJsonContent();

            // Just block here to read the json payload from the body
            using (_tracer.Step("DeploymentService.Deploy(id)"))
            {
                IActionResult result = Ok();

                try
                {
                    await _deploymentLock.LockOperationAsync(async() =>
                    {
                        try
                        {
                            if (PostDeploymentHelper.IsAutoSwapOngoing())
                            {
                                result = StatusCode(StatusCodes.Status409Conflict, Resources.Error_AutoSwapDeploymentOngoing);
                                return;
                            }

                            DeployResult deployResult;
                            if (TryParseDeployResult(id, jsonContent, out deployResult))
                            {
                                using (_tracer.Step("DeploymentService.Create(id)"))
                                {
                                    CreateDeployment(deployResult, jsonContent.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   = kUriHelper.MakeRelative(kUriHelper.GetBaseUri(Request), new Uri(Request.GetDisplayUrl()).AbsolutePath);
                                    deployResult.Url    = deploymentUri;
                                    deployResult.LogUrl = kUriHelper.MakeRelative(deploymentUri, "log");

                                    // response = Request.CreateResponse(HttpStatusCode.OK, ArmUtils.AddEnvelopeOnArmRequest(deployResult, Request));
                                    result = Ok(ArmUtils.AddEnvelopeOnArmRequest(deployResult, Request));
                                    return;
                                }
                            }

                            bool clean          = false;
                            bool needFileUpdate = true;

                            if (jsonContent != null)
                            {
                                clean = jsonContent.Value <bool>("clean");
                                JToken needFileUpdateToken;
                                if (jsonContent.TryGetValue("needFileUpdate", out needFileUpdateToken))
                                {
                                    needFileUpdate = needFileUpdateToken.Value <bool>();
                                }
                            }

                            string username = null;
                            AuthUtility.TryExtractBasicAuthUser(Request, out username);

                            IRepository repository = _repositoryFactory.GetRepository();
                            if (repository == null)
                            {
                                result = NotFound(Resources.Error_RepositoryNotFound);
                                return;
                            }
                            ChangeSet changeSet = null;
                            if (!String.IsNullOrEmpty(id))
                            {
                                changeSet = repository.GetChangeSet(id);
                                if (changeSet == null)
                                {
                                    string message = String.Format(CultureInfo.CurrentCulture, Resources.Error_DeploymentNotFound, id);
                                    result         = NotFound(message);
                                    return;
                                }
                            }

                            try
                            {
                                await _deploymentManager.DeployAsync(repository, changeSet, username, clean, deploymentInfo: null, needFileUpdate: needFileUpdate);
                            }
                            catch (DeploymentFailedException ex)
                            {
                                if (!ArmUtils.IsArmRequest(Request))
                                {
                                    throw;
                                }

                                // if requests comes thru ARM, we adjust the error code from 500 -> 400
                                result = BadRequest(ex.ToString());
                                return;
                            }

                            // 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,
                                        new PostDeploymentTraceListener(_tracer, _deploymentManager.GetLogger(changeSet.Id)));
                                }
                            }
                        }
                        catch (FileNotFoundException ex)
                        {
                            result = NotFound(ex);
                        }
                        catch (InvalidStatusException)
                        {
                            result = BadRequest("Only successful status can be active!");
                        }
                    }, "Performing deployment", TimeSpan.Zero);
                }
                catch (LockOperationException ex)
                {
                    return(StatusCode(StatusCodes.Status409Conflict, ex.Message));
                }

                return(result);
            }
        }
Example #8
0
        private static int PerformDeploy(
            string appRoot,
            string wapTargets,
            string deployer,
            string lockPath,
            IEnvironment env,
            IDeploymentSettingsManager settingsManager,
            TraceLevel level,
            ITracer tracer,
            ITraceFactory traceFactory,
            IOperationLock deploymentLock)
        {
            System.Environment.SetEnvironmentVariable("GIT_DIR", null, System.EnvironmentVariableTarget.Process);

            // Skip SSL Certificate Validate
            if (System.Environment.GetEnvironmentVariable(SettingsKeys.SkipSslValidation) == "1")
            {
                ServicePointManager.ServerCertificateValidationCallback = delegate { return(true); };
            }

            // Adjust repo path
            env.RepositoryPath = Path.Combine(env.SiteRootPath, settingsManager.GetRepositoryPath());

            string statusLockPath = Path.Combine(lockPath, Constants.StatusLockFile);
            string hooksLockPath  = Path.Combine(lockPath, Constants.HooksLockFile);


            IOperationLock statusLock = new LockFile(statusLockPath, traceFactory);
            IOperationLock hooksLock  = new LockFile(hooksLockPath, traceFactory);

            IBuildPropertyProvider buildPropertyProvider = new BuildPropertyProvider();
            ISiteBuilderFactory    builderFactory        = new SiteBuilderFactory(buildPropertyProvider, env);
            var logger = new ConsoleLogger();

            IRepository gitRepository;

            if (settingsManager.UseLibGit2SharpRepository())
            {
                gitRepository = new LibGit2SharpRepository(env, settingsManager, traceFactory);
            }
            else
            {
                gitRepository = new GitExeRepository(env, settingsManager, traceFactory);
            }

            IServerConfiguration serverConfiguration = new ServerConfiguration();
            IAnalytics           analytics           = new Analytics(settingsManager, serverConfiguration, traceFactory);

            IWebHooksManager         hooksManager            = new WebHooksManager(tracer, env, hooksLock);
            IDeploymentStatusManager deploymentStatusManager = new DeploymentStatusManager(env, analytics, statusLock);
            IDeploymentManager       deploymentManager       = new DeploymentManager(builderFactory,
                                                                                     env,
                                                                                     traceFactory,
                                                                                     analytics,
                                                                                     settingsManager,
                                                                                     deploymentStatusManager,
                                                                                     deploymentLock,
                                                                                     GetLogger(env, level, logger),
                                                                                     hooksManager);

            var step = tracer.Step(XmlTracer.ExecutingExternalProcessTrace, new Dictionary <string, string>
            {
                { "type", "process" },
                { "path", "kudu.exe" },
                { "arguments", appRoot + " " + wapTargets }
            });

            using (step)
            {
                try
                {
                    // although the api is called DeployAsync, most expensive works are done synchronously.
                    // need to launch separate task to go async explicitly (consistent with FetchDeploymentManager)
                    var deploymentTask = Task.Run(async() => await deploymentManager.DeployAsync(gitRepository, changeSet: null, deployer: deployer, clean: false));

#pragma warning disable 4014
                    // Track pending task
                    PostDeploymentHelper.TrackPendingOperation(deploymentTask, TimeSpan.Zero);
#pragma warning restore 4014

                    deploymentTask.Wait();

                    if (PostDeploymentHelper.IsAutoSwapEnabled())
                    {
                        string                branch     = settingsManager.GetBranch();
                        ChangeSet             changeSet  = gitRepository.GetChangeSet(branch);
                        IDeploymentStatusFile statusFile = deploymentStatusManager.Open(changeSet.Id);
                        if (statusFile != null && statusFile.Status == DeployStatus.Success)
                        {
                            PostDeploymentHelper.PerformAutoSwap(env.RequestId,
                                                                 new PostDeploymentTraceListener(tracer, deploymentManager.GetLogger(changeSet.Id)))
                            .Wait();
                        }
                    }
                }
                catch (Exception e)
                {
                    tracer.TraceError(e);
                    System.Console.Error.WriteLine(e.GetBaseException().Message);
                    System.Console.Error.WriteLine(Resources.Log_DeploymentError);
                    return(1);
                }
                finally
                {
                    System.Console.WriteLine("Deployment Logs : '" +
                                             env.AppBaseUrlPrefix + "/newui/jsonviewer?view_url=/api/deployments/" +
                                             gitRepository.GetChangeSet(settingsManager.GetBranch()).Id + "/log'");
                }
            }

            if (logger.HasErrors)
            {
                return(1);
            }
            tracer.Step("Perform deploy exiting successfully");
            return(0);
        }
Example #9
0
        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);
                        }
                        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)
            {
                // if last change is not null, mean there was at least one deployoment happened
                // since deployment is now done, trigger swap if enabled
                await _autoSwapHandler.HandleAutoSwap(lastChange.Id, _deploymentManager.GetLogger(lastChange.Id), _tracer);
            }
        }
        /// <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();
            }
        }
Example #11
0
        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));
                            }
                        }

                        await _deploymentManager.DeployAsync(repository, changeSet, username, clean, needFileUpdate);

                        // 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(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 Deploy(IRepository repository, ChangeSet changeSet, string deployer, bool clean, bool needFileUpdate)
        {
            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)
            {
                if (innerLogger != null)
                {
                    innerLogger.Log(ex);
                }

                if (statusFile != null)
                {
                    statusFile.MarkFailed();
                }

                tracer.TraceError(ex);

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

                throw;
            }
        }
Example #13
0
        internal async Task <ChangeSet> Sync(DropboxInfo dropboxInfo, string branch, IRepository repository)
        {
            DropboxDeployInfo deployInfo = dropboxInfo.DeployInfo;

            ResetStats();

            if (_settings.GetValue(CursorKey) != deployInfo.OldCursor)
            {
                throw new InvalidOperationException(Resources.Error_MismatchDropboxCursor);
            }

            if (!repository.IsEmpty())
            {
                // git checkout --force <branch>
                repository.ClearLock();
                repository.Update(branch);
            }

            ChangeSet changeSet = null;
            string    message   = null;

            try
            {
                using (_tracer.Step("Sync with Dropbox"))
                {
                    if (dropboxInfo.OAuthVersion == 2)
                    {
                        // Fetch the deltas
                        await UpdateDropboxDeployInfo(deployInfo);
                    }

                    // Sync dropbox => repository directory
                    await ApplyChanges(dropboxInfo, useOAuth20 : dropboxInfo.OAuthVersion == 2);
                }

                message = String.Format(CultureInfo.CurrentCulture,
                                        Resources.Dropbox_Synchronized,
                                        deployInfo.Deltas.Count);
            }
            catch (Exception)
            {
                message = String.Format(CultureInfo.CurrentCulture,
                                        Resources.Dropbox_SynchronizedWithFailure,
                                        _successCount,
                                        deployInfo.Deltas.Count,
                                        _failedCount);

                throw;
            }
            finally
            {
                Logger.Log(message);

                Logger.Log(String.Format("{0} downloaded files, {1} successful retries.", _fileCount, _retriedCount));

                IDeploymentStatusFile statusFile = _status.Open(dropboxInfo.TargetChangeset.Id);
                statusFile.UpdateMessage(message);
                statusFile.UpdateProgress(String.Format(CultureInfo.CurrentCulture, Resources.Dropbox_Committing, _successCount));

                // Commit anyway even partial change
                if (repository.Commit(message, String.Format("{0} <{1}>", deployInfo.UserName, deployInfo.Email ?? deployInfo.UserName)))
                {
                    changeSet = repository.GetChangeSet("HEAD");
                }
            }

            // Save new dropboc cursor
            LogInfo("Update dropbox cursor");
            _settings.SetValue(CursorKey, deployInfo.NewCursor);

            return(changeSet);
        }
Example #14
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);

                        if (FunctionAppHelper.LooksLikeFunctionApp() && _environment.IsOnLinuxConsumption)
                        {
                            // A Linux consumption function app deployment requires (no matter whether it is oryx build or basic deployment)
                            // 1. packaging the output folder
                            // 2. upload the artifact to user's storage account
                            // 3. reset the container workers after deployment
                            await LinuxConsumptionDeploymentHelper.SetupLinuxConsumptionFunctionAppDeployment(
                                env : _environment,
                                settings : _settings,
                                context : context,
                                shouldSyncTriggers : deploymentInfo.DoSyncTriggers,
                                shouldUpdateWebsiteRunFromPackage : deploymentInfo.OverwriteWebsiteRunFromPackage);
                        }

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

                        TouchWatchedFileIfNeeded(_settings, deploymentInfo, context);

                        if (_settings.RunFromLocalZip() && deploymentInfo is ArtifactDeploymentInfo)
                        {
                            await PostDeploymentHelper.UpdatePackageName(deploymentInfo as ArtifactDeploymentInfo, _environment, logger);
                        }

                        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);
            }
        }
        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
                                    bool isSuccess = await _deploymentManager.SendDeployStatusUpdate(updateStatusObj);

                                    if (isSuccess)
                                    {
                                        updateStatusObj = new DeployStatusApiResult(Constants.BuildRequestReceived, deploymentInfo.DeploymentTrackingId);
                                    }
                                }
                            }
                        }
                        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 #16
0
        public async Task PerformDeployment(DeploymentInfo deploymentInfo)
        {
            DateTime currentMarkerFileUTC;
            DateTime nextMarkerFileUTC = _fileSystem.File.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
                            _deploymentManager.Deploy(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 = _fileSystem.File.GetLastWriteTimeUtc(_markerFilePath);
            } while (deploymentInfo.IsReusable && currentMarkerFileUTC != nextMarkerFileUTC);
        }