예제 #1
0
        public override void ProcessRequestBase(HttpContextBase context)
        {
            using (Tracer.Step("RpcService.ReceivePack"))
            {
                // Ensure that the target directory does not have a non-Git repository.
                IRepository repository = _repositoryFactory.GetRepository();
                if (repository != null && repository.RepositoryType != RepositoryType.Git)
                {
                    context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
                    if (context.ApplicationInstance != null)
                    {
                        context.ApplicationInstance.CompleteRequest();
                    }
                    return;
                }

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

                        if (PostDeploymentHelper.IsAutoSwapOngoing())
                        {
                            context.Response.StatusCode = (int)HttpStatusCode.Conflict;
                            context.Response.Write(Resources.Error_AutoSwapDeploymentOngoing);
                            context.ApplicationInstance.CompleteRequest();
                            return;
                        }

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

                        UpdateNoCacheForResponse(context.Response);

                        // This temporary deployment is for ui purposes only, it will always be deleted via finally.
                        ChangeSet tempChangeSet;
                        using (DeploymentManager.CreateTemporaryDeployment(Resources.ReceivingChanges, out tempChangeSet))
                        {
                            GitServer.Receive(context.Request.GetInputStream(), context.Response.OutputStream);
                        }
                    }, "Handling git receive pack", TimeSpan.Zero);
                }
                catch (LockOperationException ex)
                {
                    context.Response.StatusCode = 409;
                    context.Response.Write(ex.Message);
                    context.ApplicationInstance.CompleteRequest();
                }
            }
        }
예제 #2
0
        public void IsAutoSwapOngoingOrEnabledTests()
        {
            var homePath = System.Environment.GetEnvironmentVariable("HOME");
            var tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());

            try
            {
                System.Environment.SetEnvironmentVariable("HOME", tempPath);
                System.Environment.SetEnvironmentVariable(Constants.WebSiteSwapSlotName, null);

                var autoSwapLockFile = Path.Combine(tempPath, @"site\locks", PostDeploymentHelper.AutoSwapLockFile);
                Directory.CreateDirectory(Path.GetDirectoryName(autoSwapLockFile));

                Assert.False(PostDeploymentHelper.IsAutoSwapEnabled(), "Autoswap should NOT be enabled");
                Assert.False(PostDeploymentHelper.IsAutoSwapOngoing(), "Should not be any autoswap, since it is not enabled");

                System.Environment.SetEnvironmentVariable(Constants.WebSiteSwapSlotName, "someslot");
                Assert.True(PostDeploymentHelper.IsAutoSwapEnabled(), "Autoswap should be enabled");
                Assert.False(PostDeploymentHelper.IsAutoSwapOngoing(), "Should not be any autoswap, since autoswap lock is not acquired by process.");

                File.WriteAllText(autoSwapLockFile, string.Empty);
                File.SetLastWriteTimeUtc(autoSwapLockFile, DateTime.UtcNow.AddMinutes(-3));
                Assert.False(PostDeploymentHelper.IsAutoSwapOngoing(), "Should not be any autoswap, since autoswap lock is acquired over 2 mintues ago.");

                File.WriteAllText(autoSwapLockFile, string.Empty);
                Assert.True(PostDeploymentHelper.IsAutoSwapOngoing(), "Autoswap is ongoing, since autoswap lock is acquired within 2 mintues");
            }
            finally
            {
                System.Environment.SetEnvironmentVariable("HOME", homePath);
                System.Environment.SetEnvironmentVariable(Constants.WebSiteSwapSlotName, null);
                if (Directory.Exists(tempPath))
                {
                    Directory.Delete(tempPath, recursive: true);
                }
            }
        }
예제 #3
0
        public async Task <FetchDeploymentRequestResult> FetchDeploy(
            DeploymentInfoBase deployInfo,
            bool asyncRequested,
            Uri requestUri,
            string targetBranch)
        {
            // If Scm is not enabled, we will reject all but one payload for GenericHandler
            // This is to block the unintended CI with Scm providers like GitHub
            // Since Generic payload can only be done by user action, we loosely allow
            // that and assume users know what they are doing.  Same applies to git
            // push/clone endpoint and zip deployment.
            if (!(_settings.IsScmEnabled() || deployInfo.AllowDeploymentWhileScmDisabled))
            {
                return(FetchDeploymentRequestResult.ForbiddenScmDisabled);
            }
            // Else if this app is configured with a url in WEBSITE_USE_ZIP, then fail the deployment
            // since this is a RunFromZip site and the deployment has no chance of succeeding.
            else if (_settings.RunFromRemoteZip())
            {
                return(FetchDeploymentRequestResult.ConflictRunFromRemoteZipConfigured);
            }

            // for CI payload, we will return Accepted and do the task in the BG
            // if isAsync is defined, we will return Accepted and do the task in the BG
            // since autoSwap relies on the response header, deployment has to be synchronously.
            bool isBackground = asyncRequested || deployInfo.IsContinuous;

            if (isBackground)
            {
                using (_tracer.Step("Start deployment in the background"))
                {
                    var waitForTempDeploymentCreation = asyncRequested;
                    var successfullyRequested         = await PerformBackgroundDeployment(
                        deployInfo,
                        _environment,
                        _settings,
                        _tracer.TraceLevel,
                        requestUri,
                        waitForTempDeploymentCreation);

                    return(successfullyRequested
                    ? FetchDeploymentRequestResult.RunningAynschronously
                    : FetchDeploymentRequestResult.ConflictDeploymentInProgress);
                }
            }

            _tracer.Trace("Attempting to fetch target branch {0}", targetBranch);
            try
            {
                return(await _deploymentLock.LockOperationAsync(async() =>
                {
                    if (PostDeploymentHelper.IsAutoSwapOngoing())
                    {
                        return FetchDeploymentRequestResult.ConflictAutoSwapOngoing;
                    }

                    await PerformDeployment(deployInfo);
                    return FetchDeploymentRequestResult.RanSynchronously;
                }, "Performing continuous deployment", TimeSpan.Zero));
            }
            catch (LockOperationException)
            {
                if (deployInfo.AllowDeferredDeployment)
                {
                    // Create a marker file that indicates if there's another deployment to pull
                    // because there was a deployment in progress.
                    using (_tracer.Step("Update pending deployment marker file"))
                    {
                        // REVIEW: This makes the assumption that the repository url is the same.
                        // If it isn't the result would be buggy either way.
                        FileSystemHelpers.SetLastWriteTimeUtc(_markerFilePath, DateTime.UtcNow);
                    }

                    return(FetchDeploymentRequestResult.Pending);
                }
                else
                {
                    return(FetchDeploymentRequestResult.ConflictDeploymentInProgress);
                }
            }
        }
예제 #4
0
파일: FetchHandler.cs 프로젝트: yut148/kudu
        public override async Task ProcessRequestAsync(HttpContext context)
        {
            using (_tracer.Step("FetchHandler"))
            {
                // Redirect GET /deploy requests to the Kudu root for convenience when using URL from Azure portal
                if (String.Equals(context.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
                {
                    context.Response.Redirect("~/");
                    context.ApplicationInstance.CompleteRequest();
                    return;
                }

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

                context.Response.TrySkipIisCustomErrors = true;

                DeploymentInfo deployInfo = null;

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

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

                // for CI payload, we will return Accepted and do the task in the BG
                // if isAsync is defined, we will return Accepted and do the task in the BG
                // since autoSwap relies on the response header, deployment has to be synchronously.
                bool isAsync      = String.Equals(context.Request.QueryString["isAsync"], "true", StringComparison.OrdinalIgnoreCase);
                bool isBackground = isAsync || deployInfo.IsContinuous;
                if (isBackground)
                {
                    using (_tracer.Step("Start deployment in the background"))
                    {
                        var waitForTempDeploymentCreation = isAsync;
                        await PerformBackgroundDeployment(
                            deployInfo,
                            _environment,
                            _settings,
                            _tracer.TraceLevel,
                            UriHelper.GetRequestUri(context.Request),
                            waitForTempDeploymentCreation);
                    }

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

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

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

                    // Return a http 202: the request has been accepted for processing, but the processing has not been completed.
                    context.Response.StatusCode = (int)HttpStatusCode.Accepted;
                    context.ApplicationInstance.CompleteRequest();
                }
            }
        }
예제 #5
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));
                            }
                        }

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

                            // if requests comes thru ARM, we adjust the error code from 500 -> 400
                            throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex.ToString()));
                        }

                        // auto-swap
                        if (PostDeploymentHelper.IsAutoSwapEnabled())
                        {
                            if (changeSet == null)
                            {
                                var targetBranch = _settings.GetBranch();
                                changeSet        = repository.GetChangeSet(targetBranch);
                            }

                            IDeploymentStatusFile statusFile = _status.Open(changeSet.Id);
                            if (statusFile != null && statusFile.Status == DeployStatus.Success)
                            {
                                await PostDeploymentHelper.PerformAutoSwap(_environment.RequestId, _environment.SiteRestrictedJwt, new PostDeploymentTraceListener(_tracer, _deploymentManager.GetLogger(changeSet.Id)));
                            }
                        }
                    }
                    catch (FileNotFoundException ex)
                    {
                        throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, ex));
                    }
                }, "Performing deployment");

                return(response);
            }
        }
예제 #6
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);
                }
            }
        }