File: Tasks\ZipDeploy\ZipDeploy.cs
Web Access
Project: src\src\sdk\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 System.Net;
using Microsoft.Build.Framework;
using Microsoft.NET.Sdk.Common;
using Microsoft.NET.Sdk.Publish.Tasks.Properties;

namespace Microsoft.NET.Sdk.Publish.Tasks.ZipDeploy
{
    public class ZipDeploy : Task
    {
        private const string UserAgentName = "websdk";

        [Required]
        public string? ZipToPublishPath { get; set; }

        [Required]
        public string? UserAgentVersion { get; set; }

        [Required]
        public string? DestinationUsername { get; set; }

        public string? DestinationPassword { get; set; }

        public string? PublishUrl { get; set; }

        /// <summary>
        /// Our fallback if PublishUrl is not given, which is the case for ZIP Deploy profiles
        /// profiles created prior to 15.8 Preview 4. Using this will fail if the site is a slot.
        /// </summary>
        public string? SiteName { get; set; }

        public override bool Execute()
        {
            string? user = DestinationUsername;
            string? password = DestinationPassword;

            if (string.IsNullOrEmpty(password) && !GetDestinationCredentials(out user, out password))
            {
                Log.LogError(Resources.ZIPDEPLOY_FailedToRetrieveCred);
                return false;
            }

            using (DefaultHttpClient client = new())
            {
                Task<bool> t = ZipDeployAsync(ZipToPublishPath, user, password, PublishUrl, SiteName, UserAgentVersion, client, true);
                t.Wait();
                return t.Result;
            }
        }

        public async Task<bool> ZipDeployAsync(string? zipToPublishPath, string? userName, string? password, string? publishUrl, string? siteName, string? userAgentVersion, IHttpClient client, bool logMessages)
        {
            if (!File.Exists(zipToPublishPath) || client == null)
            {
                return false;
            }

            string? zipDeployPublishUrl = null;

            if (publishUrl is not null && publishUrl.Length != 0)
            {
                if (!publishUrl.EndsWith("/"))
                {
                    publishUrl += "/";
                }

                zipDeployPublishUrl = publishUrl + "api/zipdeploy";
            }
            else if (!string.IsNullOrEmpty(siteName))
            {
                zipDeployPublishUrl = $"https://{siteName}.scm.azurewebsites.net/api/zipdeploy";
            }
            else
            {
                if (logMessages)
                {
                    Log.LogError(Resources.ZIPDEPLOY_InvalidSiteNamePublishUrl);
                }

                return false;
            }

            if (logMessages)
            {
                Log.LogMessage(MessageImportance.High, string.Format(Resources.ZIPDEPLOY_PublishingZip, zipToPublishPath, zipDeployPublishUrl));
            }

            // use the async version of the api
            Uri uri = new($"{zipDeployPublishUrl}?isAsync=true", UriKind.Absolute);
            string userAgent = $"{UserAgentName}/{userAgentVersion}";
            FileStream stream = File.OpenRead(zipToPublishPath);
            IHttpResponse? response = await client.PostRequestAsync(uri, userName, password, "application/zip", userAgent, Encoding.UTF8, stream);
            if (response?.StatusCode != HttpStatusCode.OK && response?.StatusCode != HttpStatusCode.Accepted)
            {
                if (logMessages)
                {
                    Log.LogError(string.Format(Resources.ZIPDEPLOY_FailedDeploy, zipDeployPublishUrl, response?.StatusCode));
                }

                return false;
            }
            else
            {
                if (logMessages)
                {
                    Log.LogMessage(Resources.ZIPDEPLOY_Uploaded);
                }

                string? deploymentUrl = response.GetHeader("Location").FirstOrDefault();
                if (!string.IsNullOrEmpty(deploymentUrl))
                {
                    ZipDeploymentStatus deploymentStatus = new(client, userAgent, Log, logMessages);

                    DeploymentResponse? deploymentResponse = await deploymentStatus.PollDeploymentStatusAsync(deploymentUrl, userName, password);
                    if (deploymentResponse?.Status == DeployStatus.Success)
                    {
                        Log.LogMessage(MessageImportance.High, Resources.ZIPDEPLOY_Succeeded);
                        return true;
                    }
                    else if (deploymentResponse is null || deploymentResponse?.Status == DeployStatus.Failed || deploymentResponse?.Status == DeployStatus.Unknown)
                    {
                        Log.LogError(string.Format(Resources.ZIPDEPLOY_FailedDeployWithLogs,
                            zipDeployPublishUrl,
                            deploymentResponse?.Status ?? DeployStatus.Unknown,
                            deploymentResponse?.GetLogUrlWithId()));

                        return false;
                    }
                }
            }

            return true;
        }

        private bool GetDestinationCredentials(out string user, out string password)
        {
            VSHostObject hostObj = new(HostObject, Log);
            if (hostObj.TryGetCredentials() is (string u, string p))
            {
                user = u;
                password = p;

                return true;
            }

            user = string.Empty;
            password = string.Empty;

            return false;
        }
    }
}