File: Tasks\OneDeploy\OneDeploy.cs
Web Access
Project: ..\..\..\src\WebSdk\Publish\Tasks\Microsoft.NET.Sdk.Publish.Tasks.csproj (Microsoft.NET.Sdk.Publish.Tasks)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.Build.Framework;
using Microsoft.NET.Sdk.Publish.Tasks.MsDeploy;
using Microsoft.NET.Sdk.Publish.Tasks.Properties;
 
namespace Microsoft.NET.Sdk.Publish.Tasks.OneDeploy;
 
/// <summary>
/// 'OneDeploy' publish task.
/// </summary>
public partial class OneDeploy : Task
{
    private const string DefaultRequestContentType = "application/zip";
    private const string UserAgentName = "websdk";
    private const string OneDeployApiPath = "api/publish";
    private const string OneDeployQueryParam = "RemoteBuild=false";
 
    private readonly ITaskLogger _taskLogger;
 
    public OneDeploy()
    {
        // logger is enabled by default
        _taskLogger = new TaskLogger(Log, true);
    }
 
    // Test constructor
    internal OneDeploy(ITaskLogger taskLogger)
    {
        _taskLogger = taskLogger;
    }
 
    [Required]
    public string? FileToPublishPath { get; set; }
 
    public string? PublishUrl { get; set; }
 
    [Required]
    public string? Username { get; set; }
 
    public string? Password { get; set; }
 
    [Required]
    public string? UserAgentVersion { get; set; }
 
    /// <inheritdoc/>
    public override bool Execute()
    {
        var deployTask = OneDeployAsync(FileToPublishPath, Username, Password, PublishUrl, UserAgentVersion, WebJobName, WebJobType);
        deployTask.Wait();
 
        return deployTask.Result;
    }
 
    public async Task<bool> OneDeployAsync(
        string? fileToPublishPath,
        string? username,
        string? password,
        string? url,
        string? userAgentVersion,
        string? webjobName = null,
        string? webjobType = null,
        CancellationToken cancellationToken = default)
    {
        using DefaultHttpClient httpClient = new();
 
        var userAgent = $"{UserAgentName}/{userAgentVersion}";
        var deployStatusService = new OneDeployStatusService(_taskLogger);
 
        return await OneDeployAsync(
            fileToPublishPath, username, password, url, userAgent, webjobName, webjobType, httpClient, deployStatusService, cancellationToken);
    }
 
    internal async Task<bool> OneDeployAsync(
        string? fileToPublishPath,
        string? username,
        string? password,
        string? url,
        string userAgent,
        string? webjobName,
        string? webjobType,
        IHttpClient httpClient,
        IDeploymentStatusService<DeploymentResponse> deploymentStatusService,
        CancellationToken cancellationToken = default)
    {
        // missing credentials
        if (string.IsNullOrEmpty(password) && !GetCredentialsFromTask(out username, out password))
        {
            _taskLogger.LogError(Resources.ONEDEPLOY_FailedToRetrieveCredentials);
            return false;
        }
 
        // missing file to publish
        if (!File.Exists(fileToPublishPath))
        {
            _taskLogger.LogError(Resources.ONEDEPLOY_FileToPublish_NotFound);
            return false;
        }
 
        // 'PublishUrl' must be valid
        if (!GetPublishUrl(url, webjobName, webjobType, out var oneDeployPublishUri) || oneDeployPublishUri is null)
        {
            _taskLogger.LogError(string.Format(Resources.ONEDEPLOY_InvalidPublishUrl, url));
            return false;
        }
 
        _taskLogger.LogMessage(
            MessageImportance.High,
            string.Format(Resources.ONEDEPLOY_PublishingOneDeploy, fileToPublishPath, oneDeployPublishUri.ToString()));
 
        var fileToPublishStream = File.OpenRead(fileToPublishPath);
 
        // push package to target instance
        var response = await DeployAsync(
            httpClient, oneDeployPublishUri, username, password, userAgent, fileToPublishPath, webjobName, webjobType, fileToPublishStream, cancellationToken);
 
        // if push failed, finish operation
        if (!(response?.IsResponseSuccessful() ?? false))
        {
            var responseText = response is null ? string.Empty : await response.GetTextResponseAsync(cancellationToken);
 
            var errorMessage = !string.IsNullOrEmpty(responseText)
                ? string.Format(Resources.ONEDEPLOY_FailedDeployRequest_With_ResponseText, oneDeployPublishUri, response?.StatusCode, responseText)
                : string.Format(Resources.ONEDEPLOY_FailedDeployRequest, oneDeployPublishUri, response?.StatusCode);
 
            _taskLogger.LogError(errorMessage);
            return false;
        }
 
        _taskLogger.LogMessage(string.Format(Resources.ONEDEPLOY_Uploaded, fileToPublishPath));
 
        // monitor deployment (if available)
        var deploymentUrl = response.GetHeader("Location").FirstOrDefault();
        if (!string.IsNullOrEmpty(deploymentUrl) && Uri.TryCreate(deploymentUrl, UriKind.Absolute, out var _))
        {
            var deploymentResponse = await deploymentStatusService.PollDeploymentAsync(httpClient, deploymentUrl, username, password, userAgent, cancellationToken);
 
            if (deploymentResponse?.IsSuccessfulResponse() ?? false)
            {
                _taskLogger.LogMessage(MessageImportance.High, Resources.ONEDEPLOY_Success);
                return true;
            }
 
            if (deploymentResponse?.IsFailedResponse() ?? true)
            {
                _taskLogger.LogError(string.Format(Resources.ONEDEPLOY_FailedWithLogs,
                    fileToPublishPath,
                    oneDeployPublishUri,
                    deploymentResponse?.Status ?? DeploymentStatus.Unknown,
                    deploymentResponse?.GetLogUrlWithId()));
 
                return false;
            }
        }
 
        // no follow-up deployment response (as in WebJobs); assume deployment worked
        _taskLogger.LogMessage(MessageImportance.High, Resources.ONEDEPLOY_Success);
        return true;
    }
 
    private bool GetCredentialsFromTask(out string user, out string password)
    {
        VSHostObject hostObj = new(HostObject as IEnumerable<ITaskItem>);
 
        return hostObj.ExtractCredentials(out user, out password);
    }
 
    private Task<IHttpResponse?> DeployAsync(
        IHttpClient httpClient,
        Uri publishUri,
        string? username,
        string? password,
        string? userAgent,
        string? fileToPublish,
        string? webjobName,
        string? webjobType,
        FileStream fileToPublishStream,
        CancellationToken cancellationToken)
    {
        return IsWebJobProject(webjobName, webjobType)
           ? DeployWebJobAsync(httpClient, publishUri, username, password, userAgent, fileToPublish, fileToPublishStream, cancellationToken)
           : DefaultDeployAsync(httpClient, publishUri, username, password, userAgent, fileToPublishStream, cancellationToken);
    }
 
    private bool GetPublishUrl(string? publishUrl, string? webjobName, string? webjobType, out Uri? publishUri)
    {
        publishUri = null;
 
        if (publishUrl is null || publishUrl.Length == 0 ||
            !Uri.TryCreate(publishUrl, UriKind.Absolute, out var _))
        {
            return false;
        }
 
        return IsWebJobProject(webjobName, webjobType)
            ? GetWebJobPublishUri(publishUrl, webjobName, webjobType, out publishUri)
            : GetDefaultPublishUri(publishUrl, out publishUri);
    }
 
    private async Task<IHttpResponse?> DefaultDeployAsync(
        IHttpClient httpClient,
        Uri publishUri,
        string? username,
        string? password,
        string? userAgent,
        FileStream fileToPublishStream,
        CancellationToken cancellationToken)
    {
        var response = await httpClient.PostRequestAsync(
            publishUri, username, password, DefaultRequestContentType, userAgent, Encoding.UTF8, fileToPublishStream);
 
        return response;
    }
 
    private bool GetDefaultPublishUri(string publishUrl, out Uri publishUri)
    {
        publishUri = new UriBuilder(publishUrl)
        {
            Path = OneDeployApiPath,
            Query = OneDeployQueryParam,
            Port = -1
        }.Uri;
 
        return Uri.TryCreate(publishUri.ToString(), UriKind.Absolute, out var _);
    }
}