示例#1
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)));
                }
            }
        }
示例#2
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, _tracer);
                        }
                        catch (BranchNotFoundException)
                        {
                            // mark no deployment is needed
                            deploymentInfo.TargetChangeset = null;
                        }

                        // set to null as Deploy() below takes over logging
                        innerLogger = null;

                        // The branch or commit id to deploy
                        string deployBranch = !String.IsNullOrEmpty(deploymentInfo.CommitId) ? deploymentInfo.CommitId : targetBranch;

                        // In case the commit or perhaps fetch do no-op.
                        if (deploymentInfo.TargetChangeset != null && ShouldDeploy(repository, deploymentInfo, deployBranch))
                        {
                            // Perform the actual deployment
                            var changeSet = repository.GetChangeSet(deployBranch);

                            if (changeSet == null && !String.IsNullOrEmpty(deploymentInfo.CommitId))
                            {
                                throw new InvalidOperationException(String.Format("Invalid revision '{0}'!", deploymentInfo.CommitId));
                            }

                            lastChange = changeSet;

                            // Here, we don't need to update the working files, since we know Fetch left them in the correct state
                            // unless for GenericHandler where specific commitId is specified
                            bool deploySpecificCommitId = !String.IsNullOrEmpty(deploymentInfo.CommitId);

                            await _deploymentManager.DeployAsync(repository, changeSet, deploymentInfo.Deployer, clean : false, needFileUpdate : deploySpecificCommitId);
                        }
                    }
                    catch (Exception ex)
                    {
                        if (innerLogger != null)
                        {
                            innerLogger.Log(ex);
                        }

                        // In case the commit or perhaps fetch do no-op.
                        if (deploymentInfo.TargetChangeset != null)
                        {
                            IDeploymentStatusFile statusFile = _status.Open(deploymentInfo.TargetChangeset.Id);
                            if (statusFile != null)
                            {
                                statusFile.MarkFailed();
                            }
                        }

                        throw;
                    }

                    // only clean up temp deployment if successful
                    tempDeployment.Dispose();
                }

                // check marker file and, if changed (meaning new /deploy request), redeploy.
                nextMarkerFileUTC = FileSystemHelpers.GetLastWriteTimeUtc(_markerFilePath);
            } while (deploymentInfo.IsReusable && currentMarkerFileUTC != nextMarkerFileUTC);

            if (lastChange != null && PostDeploymentHelper.IsAutoSwapEnabled())
            {
                IDeploymentStatusFile statusFile = _status.Open(lastChange.Id);
                if (statusFile.Status == DeployStatus.Success)
                {
                    // if last change is not null and finish successfully, mean there was at least one deployoment happened
                    // since deployment is now done, trigger swap if enabled
                    await PostDeploymentHelper.PerformAutoSwap(_environment.RequestId, _environment.SiteRestrictedJwt, new PostDeploymentTraceListener(_tracer, _deploymentManager.GetLogger(lastChange.Id)));
                }
            }
        }
示例#3
0
        public override async Task ProcessRequestAsync(HttpContext context)
        {
            using (_tracer.Step("FetchHandler"))
            {
                // Redirect GET /deploy requests to the Kudu root for convenience when using URL from Azure portal
                if (String.Equals(context.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
                {
                    context.Response.Redirect("~/");
                    context.ApplicationInstance.CompleteRequest();
                    return;
                }

                if (!String.Equals(context.Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase))
                {
                    context.Response.StatusCode = (int)HttpStatusCode.NotFound;
                    context.ApplicationInstance.CompleteRequest();
                    return;
                }

                context.Response.TrySkipIisCustomErrors = true;

                DeploymentInfo deployInfo = null;

                // We are going to assume that the branch details are already set by the time it gets here. This is particularly important in the mercurial case,
                // since Settings hardcodes the default value for Branch to be "master". Consequently, Kudu will NoOp requests for Mercurial commits.
                string targetBranch = _settings.GetBranch();
                try
                {
                    var          request = new HttpRequestWrapper(context.Request);
                    JObject      payload = GetPayload(request);
                    DeployAction action  = GetRepositoryInfo(request, payload, targetBranch, out deployInfo);
                    if (action == DeployAction.NoOp)
                    {
                        return;
                    }

                    // If Scm is not enabled, we will reject all but one payload for GenericHandler
                    // This is to block the unintended CI with Scm providers like GitHub
                    // Since Generic payload can only be done by user action, we loosely allow
                    // that and assume users know what they are doing.  Same applies to git
                    // push/clone endpoint.
                    if (!_settings.IsScmEnabled() && !(deployInfo.Handler is GenericHandler || deployInfo.Handler is DropboxHandler))
                    {
                        context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                        context.ApplicationInstance.CompleteRequest();
                        return;
                    }
                }
                catch (FormatException ex)
                {
                    _tracer.TraceError(ex);
                    context.Response.StatusCode = 400;
                    context.Response.Write(ex.Message);
                    context.ApplicationInstance.CompleteRequest();
                    return;
                }

                // for CI payload, we will return Accepted and do the task in the BG
                // if isAsync is defined, we will return Accepted and do the task in the BG
                // since autoSwap relies on the response header, deployment has to be synchronously.
                bool isAsync      = String.Equals(context.Request.QueryString["isAsync"], "true", StringComparison.OrdinalIgnoreCase);
                bool isBackground = (isAsync || deployInfo.IsContinuous) && !_autoSwapHandler.IsAutoSwapEnabled();
                if (isBackground)
                {
                    using (_tracer.Step("Start deployment in the background"))
                    {
                        ChangeSet   tempChangeSet  = null;
                        IDisposable tempDeployment = null;
                        if (isAsync)
                        {
                            // create temporary deployment before the actual deployment item started
                            // this allows portal ui to readily display on-going deployment (not having to wait for fetch to complete).
                            // in addition, it captures any failure that may occur before the actual deployment item started
                            tempDeployment = _deploymentManager.CreateTemporaryDeployment(
                                Resources.ReceivingChanges,
                                out tempChangeSet,
                                deployInfo.TargetChangeset,
                                deployInfo.Deployer);
                        }

                        PerformBackgroundDeployment(deployInfo, _environment, _settings, _tracer.TraceLevel, context.Request.Url, tempDeployment, tempChangeSet);
                    }

                    // to avoid regression, only set location header if isAsync
                    if (isAsync)
                    {
                        // latest deployment keyword reserved to poll till deployment done
                        context.Response.Headers["Location"] = new Uri(context.Request.Url,
                                                                       String.Format("/api/deployments/{0}?deployer={1}&time={2}", Constants.LatestDeployment, deployInfo.Deployer, DateTime.UtcNow.ToString("yyy-MM-dd_HH-mm-ssZ"))).ToString();
                    }
                    context.Response.StatusCode = (int)HttpStatusCode.Accepted;
                    context.ApplicationInstance.CompleteRequest();
                    return;
                }

                _tracer.Trace("Attempting to fetch target branch {0}", targetBranch);
                bool acquired = await _deploymentLock.TryLockOperationAsync(async() =>
                {
                    if (_autoSwapHandler.IsAutoSwapOngoing())
                    {
                        context.Response.StatusCode = (int)HttpStatusCode.Conflict;
                        context.Response.Write(Resources.Error_AutoSwapDeploymentOngoing);
                        context.ApplicationInstance.CompleteRequest();
                        return;
                    }

                    await PerformDeployment(deployInfo);

                    _autoSwapHandler.HandleAutoSwap();
                }, TimeSpan.Zero);

                if (!acquired)
                {
                    // Create a marker file that indicates if there's another deployment to pull
                    // because there was a deployment in progress.
                    using (_tracer.Step("Update pending deployment marker file"))
                    {
                        // REVIEW: This makes the assumption that the repository url is the same.
                        // If it isn't the result would be buggy either way.
                        FileSystemHelpers.SetLastWriteTimeUtc(_markerFilePath, DateTime.UtcNow);
                    }

                    // Return a http 202: the request has been accepted for processing, but the processing has not been completed.
                    context.Response.StatusCode = (int)HttpStatusCode.Accepted;
                    context.ApplicationInstance.CompleteRequest();
                }
            }
        }
示例#4
0
        public async Task PerformDeployment(DeploymentInfo deploymentInfo)
        {
            DateTime currentMarkerFileUTC;
            DateTime nextMarkerFileUTC = FileSystemHelpers.GetLastWriteTimeUtc(_markerFilePath);

            do
            {
                // save the current marker
                currentMarkerFileUTC = nextMarkerFileUTC;

                string targetBranch = _settings.GetBranch();

                using (_tracer.Step("Performing fetch based deployment"))
                {
                    // create temporary deployment before the actual deployment item started
                    // this allows portal ui to readily display on-going deployment (not having to wait for fetch to complete).
                    // in addition, it captures any failure that may occur before the actual deployment item started
                    ChangeSet   tempChangeSet;
                    IDisposable tempDeployment = _deploymentManager.CreateTemporaryDeployment(
                        Resources.ReceivingChanges,
                        out tempChangeSet,
                        deploymentInfo.TargetChangeset,
                        deploymentInfo.Deployer);

                    ILogger innerLogger = null;
                    try
                    {
                        ILogger logger = _deploymentManager.GetLogger(tempChangeSet.Id);

                        // Fetch changes from the repository
                        innerLogger = logger.Log(Resources.FetchingChanges);

                        IRepository repository = _repositoryFactory.EnsureRepository(deploymentInfo.RepositoryType);

                        try
                        {
                            await deploymentInfo.Handler.Fetch(repository, deploymentInfo, targetBranch, innerLogger);
                        }
                        catch (BranchNotFoundException)
                        {
                            // mark no deployment is needed
                            deploymentInfo.TargetChangeset = null;
                        }

                        // set to null as Deploy() below takes over logging
                        innerLogger = null;

                        // In case the commit or perhaps fetch do no-op.
                        if (deploymentInfo.TargetChangeset != null && ShouldDeploy(repository, deploymentInfo, targetBranch))
                        {
                            // Perform the actual deployment
                            var changeSet = repository.GetChangeSet(targetBranch);

                            // Here, we don't need to update the working files, since we know Fetch left them in the correct state
                            await _deploymentManager.DeployAsync(repository, changeSet, deploymentInfo.Deployer, clean : false, needFileUpdate : false);
                        }
                    }
                    catch (Exception ex)
                    {
                        if (innerLogger != null)
                        {
                            innerLogger.Log(ex);
                        }

                        // In case the commit or perhaps fetch do no-op.
                        if (deploymentInfo.TargetChangeset != null)
                        {
                            IDeploymentStatusFile statusFile = _status.Open(deploymentInfo.TargetChangeset.Id);
                            if (statusFile != null)
                            {
                                statusFile.MarkFailed();
                            }
                        }

                        throw;
                    }

                    // only clean up temp deployment if successful
                    tempDeployment.Dispose();
                }

                // check marker file and, if changed (meaning new /deploy request), redeploy.
                nextMarkerFileUTC = FileSystemHelpers.GetLastWriteTimeUtc(_markerFilePath);
            } while (deploymentInfo.IsReusable && currentMarkerFileUTC != nextMarkerFileUTC);
        }
示例#5
0
        public async Task Invoke(
            HttpContext context,
            ITracer tracer,
            IGitServer gitServer,
            IDictionary <string, IOperationLock> namedLocks,
            IDeploymentManager deploymentManager,
            IRepositoryFactory repositoryFactory,
            IEnvironment environment)
        {
            //Get the deployment lock from the locks dictionary
            var deploymentLock = namedLocks["deployment"];

            using (tracer.Step("RpcService.ReceivePack"))
            {
                // Ensure that the target directory does not have a non-Git repository.
                IRepository repository = repositoryFactory.GetRepository();
                if (repository != null && repository.RepositoryType != RepositoryType.Git)
                {
                    context.Response.StatusCode = StatusCodes.Status400BadRequest;
                    return;
                }

                try
                {
                    await deploymentLock.LockOperationAsync(() =>
                    {
                        context.Response.ContentType = "application/x-git-receive-pack-result";

                        if (PostDeploymentHelper.IsAutoSwapOngoing())
                        {
                            context.Response.StatusCode = StatusCodes.Status409Conflict;
                            var msg = Encoding.UTF8.GetBytes(Resources.Error_AutoSwapDeploymentOngoing);
                            return(context.Response.Body.WriteAsync(msg, 0, msg.Length));
                        }

                        string username = null;
                        if (AuthUtility.TryExtractBasicAuthUser(context.Request, out username))
                        {
                            gitServer.SetDeployer(username);
                        }

                        UpdateNoCacheForResponse(context.Response);

                        // This temporary deployment is for ui purposes only, it will always be deleted via finally.
                        ChangeSet tempChangeSet;
                        using (deploymentManager.CreateTemporaryDeployment(Resources.ReceivingChanges, out tempChangeSet))
                        {
                            // to pass to kudu.exe post receive hook
                            System.Environment.SetEnvironmentVariable(Constants.RequestIdHeader, environment.RequestId);
                            try
                            {
                                gitServer.Receive(context.Request.Body, context.Response.Body);
                            }
                            finally
                            {
                                System.Environment.SetEnvironmentVariable(Constants.RequestIdHeader, null);
                            }
                        }

                        return(Task.CompletedTask);
                    }, "Handling git receive pack", TimeSpan.Zero);
                }
                catch (LockOperationException ex)
                {
                    context.Response.StatusCode = StatusCodes.Status409Conflict;
                    var msg = Encoding.UTF8.GetBytes(ex.Message);
                    await context.Response.Body.WriteAsync(msg, 0, msg.Length);
                }
            }
        }