diff --git a/sdk.sln b/sdk.sln
index c57d23959f8c..0b5a7846a285 100644
--- a/sdk.sln
+++ b/sdk.sln
@@ -211,6 +211,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PublishProfiles", "PublishP
src\WebSdk\Publish\Targets\PublishProfiles\DefaultMSDeploy.pubxml = src\WebSdk\Publish\Targets\PublishProfiles\DefaultMSDeploy.pubxml
src\WebSdk\Publish\Targets\PublishProfiles\DefaultMSDeployPackage.pubxml = src\WebSdk\Publish\Targets\PublishProfiles\DefaultMSDeployPackage.pubxml
src\WebSdk\Publish\Targets\PublishProfiles\DefaultZipDeploy.pubxml = src\WebSdk\Publish\Targets\PublishProfiles\DefaultZipDeploy.pubxml
+ src\WebSdk\Publish\Targets\PublishProfiles\DefaultOneDeploy.pubxml = src\WebSdk\Publish\Targets\PublishProfiles\DefaultOneDeploy.pubxml
+ src\WebSdk\Publish\Targets\PublishProfiles\DefaultWebJobOneDeploy.pubxml = src\WebSdk\Publish\Targets\PublishProfiles\DefaultWebJobOneDeploy.pubxml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PublishTargets", "PublishTargets", "{40E6E4B1-286B-4542-B814-2A3DA29510D1}"
@@ -221,6 +223,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PublishTargets", "PublishTa
src\WebSdk\Publish\Targets\PublishTargets\Microsoft.NET.Sdk.Publish.MSDeploy.targets = src\WebSdk\Publish\Targets\PublishTargets\Microsoft.NET.Sdk.Publish.MSDeploy.targets
src\WebSdk\Publish\Targets\PublishTargets\Microsoft.NET.Sdk.Publish.MSDeployPackage.targets = src\WebSdk\Publish\Targets\PublishTargets\Microsoft.NET.Sdk.Publish.MSDeployPackage.targets
src\WebSdk\Publish\Targets\PublishTargets\Microsoft.NET.Sdk.Publish.ZipDeploy.targets = src\WebSdk\Publish\Targets\PublishTargets\Microsoft.NET.Sdk.Publish.ZipDeploy.targets
+ src\WebSdk\Publish\Targets\PublishTargets\Microsoft.NET.Sdk.Publish.OneDeploy.targets = src\WebSdk\Publish\Targets\PublishTargets\Microsoft.NET.Sdk.Publish.OneDeploy.targets
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TransformTargets", "TransformTargets", "{DFA91CC3-D6E4-45B7-AF6F-4385288886E4}"
diff --git a/src/WebSdk/Publish/Targets/CopyTargets/Microsoft.NET.Sdk.Publish.CopyFiles.targets b/src/WebSdk/Publish/Targets/CopyTargets/Microsoft.NET.Sdk.Publish.CopyFiles.targets
index 95c3483903f4..e7317f6bb3ad 100644
--- a/src/WebSdk/Publish/Targets/CopyTargets/Microsoft.NET.Sdk.Publish.CopyFiles.targets
+++ b/src/WebSdk/Publish/Targets/CopyTargets/Microsoft.NET.Sdk.Publish.CopyFiles.targets
@@ -113,6 +113,7 @@ Copyright (C) Microsoft Corporation. All rights reserved.
$(PublishIntermediateOutputPath)\app_data\Jobs\$(WebJobType)\$(WebJobName)\
+ $(PublishIntermediateOutputPath)\
$(PublishConfiguration)
diff --git a/src/WebSdk/Publish/Targets/PublishProfiles/DefaultOneDeploy.pubxml b/src/WebSdk/Publish/Targets/PublishProfiles/DefaultOneDeploy.pubxml
new file mode 100644
index 000000000000..84faff9a442e
--- /dev/null
+++ b/src/WebSdk/Publish/Targets/PublishProfiles/DefaultOneDeploy.pubxml
@@ -0,0 +1,13 @@
+
+
+
+
+ OneDeploy
+ AzureWebSite
+ Release
+ Any CPU
+
+
+
diff --git a/src/WebSdk/Publish/Targets/PublishProfiles/DefaultWebJobOneDeploy.pubxml b/src/WebSdk/Publish/Targets/PublishProfiles/DefaultWebJobOneDeploy.pubxml
new file mode 100644
index 000000000000..5c123b76b436
--- /dev/null
+++ b/src/WebSdk/Publish/Targets/PublishProfiles/DefaultWebJobOneDeploy.pubxml
@@ -0,0 +1,15 @@
+
+
+
+
+ OneDeploy
+ AzureWebSite
+ Release
+ Any CPU
+
+
+
+
+
diff --git a/src/WebSdk/Publish/Targets/PublishTargets/Microsoft.NET.Sdk.Publish.OneDeploy.targets b/src/WebSdk/Publish/Targets/PublishTargets/Microsoft.NET.Sdk.Publish.OneDeploy.targets
new file mode 100644
index 000000000000..8c3325500638
--- /dev/null
+++ b/src/WebSdk/Publish/Targets/PublishTargets/Microsoft.NET.Sdk.Publish.OneDeploy.targets
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+ <_DotNetPublishFiles>
+ OneDeploy;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ WebSdk
+ WebSdk_VisualStudio_$(VisualStudioVersion)
+
+
+
+
+
+
+
+
+
+
diff --git a/src/WebSdk/Publish/Targets/TransformTargets/Microsoft.NET.Sdk.Publish.TransformFiles.targets b/src/WebSdk/Publish/Targets/TransformTargets/Microsoft.NET.Sdk.Publish.TransformFiles.targets
index f626ff306623..4125838cd71b 100644
--- a/src/WebSdk/Publish/Targets/TransformTargets/Microsoft.NET.Sdk.Publish.TransformFiles.targets
+++ b/src/WebSdk/Publish/Targets/TransformTargets/Microsoft.NET.Sdk.Publish.TransformFiles.targets
@@ -245,13 +245,15 @@ Copyright (C) Microsoft Corporation. All rights reserved.
<_UseAppHost Condition=" '$(_UseAppHost)' == '' Or '$(RuntimeIdentifier)' == '' ">false
<_ExecutableExtension Condition=" '$(_ExecutableExtension)' == '' And $(RuntimeIdentifier.StartsWith('win')) ">.exe
<_IsLinux Condition=" '$(IsLinux)' == 'True'">true
+ <_WebJobsDirectory>$(PublishIntermediateOutputPath)\app_data\Jobs\$(WebJobType)\$(WebJobName)\
+ <_WebJobsDirectory Condition="'$(WebPublishMethod)' == 'OneDeploy'">$(PublishIntermediateOutputPath)\
diff --git a/src/WebSdk/Publish/Tasks/Properties/AssemblyInfo.cs b/src/WebSdk/Publish/Tasks/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000000..94ef778a731a
--- /dev/null
+++ b/src/WebSdk/Publish/Tasks/Properties/AssemblyInfo.cs
@@ -0,0 +1,8 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.CompilerServices;
+
+// Test assemblies
+[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
+[assembly: InternalsVisibleTo("Microsoft.NET.Sdk.Publish.Tasks.Tests, PublicKey = 0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
diff --git a/src/WebSdk/Publish/Tasks/Properties/Resources.Designer.cs b/src/WebSdk/Publish/Tasks/Properties/Resources.Designer.cs
index 2577a999896f..6da73efa8d6a 100644
--- a/src/WebSdk/Publish/Tasks/Properties/Resources.Designer.cs
+++ b/src/WebSdk/Publish/Tasks/Properties/Resources.Designer.cs
@@ -444,6 +444,42 @@ public static string DeploymentError_MissingDbDacFx {
}
}
+ ///
+ /// Looks up a localized string similar to Deployment status is '{0}'..
+ ///
+ public static string DeploymentStatus {
+ get {
+ return ResourceManager.GetString("DeploymentStatus", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Deployment status URL '{0}' is missing or invalid.
+ ///
+ public static string DeploymentStatus_InvalidPollingUrl {
+ get {
+ return ResourceManager.GetString("DeploymentStatus_InvalidPollingUrl", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Polling for deployment status....
+ ///
+ public static string DeploymentStatus_Polling {
+ get {
+ return ResourceManager.GetString("DeploymentStatus_Polling", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Deployment status is '{0}': {1}.
+ ///
+ public static string DeploymentStatusWithText {
+ get {
+ return ResourceManager.GetString("DeploymentStatusWithText", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Generating Entity Framework SQL Scripts....
///
@@ -673,6 +709,87 @@ public static string MsDeployReadMe {
}
}
+ ///
+ /// Looks up a localized string similar to OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'..
+ ///
+ public static string ONEDEPLOY_FailedDeployRequest {
+ get {
+ return ResourceManager.GetString("ONEDEPLOY_FailedDeployRequest", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}..
+ ///
+ public static string ONEDEPLOY_FailedDeployRequest_With_ResponseText {
+ get {
+ return ResourceManager.GetString("ONEDEPLOY_FailedDeployRequest_With_ResponseText", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Failed to retrieve publish credentials..
+ ///
+ public static string ONEDEPLOY_FailedToRetrieveCredentials {
+ get {
+ return ResourceManager.GetString("ONEDEPLOY_FailedToRetrieveCredentials", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'..
+ ///
+ public static string ONEDEPLOY_FailedWithLogs {
+ get {
+ return ResourceManager.GetString("ONEDEPLOY_FailedWithLogs", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to File to publish '{0}' was not found or is not accessible..
+ ///
+ public static string ONEDEPLOY_FileToPublish_NotFound {
+ get {
+ return ResourceManager.GetString("ONEDEPLOY_FileToPublish_NotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to PublishUrl '{0}' is missing or has an invalid value..
+ ///
+ public static string ONEDEPLOY_InvalidPublishUrl {
+ get {
+ return ResourceManager.GetString("ONEDEPLOY_InvalidPublishUrl", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Publishing '{0}' to '{1}'....
+ ///
+ public static string ONEDEPLOY_PublishingOneDeploy {
+ get {
+ return ResourceManager.GetString("ONEDEPLOY_PublishingOneDeploy", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to OneDeploy deployment succeeded..
+ ///
+ public static string ONEDEPLOY_Success {
+ get {
+ return ResourceManager.GetString("ONEDEPLOY_Success", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to File '{0}' uploaded to target instance..
+ ///
+ public static string ONEDEPLOY_Uploaded {
+ get {
+ return ResourceManager.GetString("ONEDEPLOY_Uploaded", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Unable to parse the pubxml file {0}..
///
diff --git a/src/WebSdk/Publish/Tasks/Properties/Resources.resx b/src/WebSdk/Publish/Tasks/Properties/Resources.resx
index ce720d327efa..f7472535d0a6 100644
--- a/src/WebSdk/Publish/Tasks/Properties/Resources.resx
+++ b/src/WebSdk/Publish/Tasks/Properties/Resources.resx
@@ -1203,4 +1203,53 @@ For more information on this deploy script visit: https://go.microsoft.com/fwlin
The attempt to publish the ZIP file through '{0}' failed with HTTP status code '{1}'.
{0} - URL to deploy zip, {1} - HTTP response code
+
+ Failed to retrieve publish credentials.
+
+
+ File to publish '{0}' was not found or is not accessible.
+ {0} - file to publish
+
+
+ PublishUrl '{0}' is missing or has an invalid value.
+ {0} - publish URL
+
+
+ Publishing '{0}' to '{1}'...
+ {0} - file to publish, {1} - publish URL
+
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ {0} - publish URL, {1} - HTTP response status code
+
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ {0} - publish URL, {1} - HTTP response status code, {2} - response body as text
+
+
+ File '{0}' uploaded to target instance.
+ {0} - file to publish.
+
+
+ OneDeploy deployment succeeded.
+
+
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ {0} - file to publish, {1} - publish URL, {2} - deployment response status code, {3} - deployment logs URL
+
+
+ Polling for deployment status...
+
+
+ Deployment status URL '{0}' is missing or invalid
+ {0} - deployment polling URL
+
+
+ Deployment status is '{0}'.
+ {0} - deployment status name
+
+
+ Deployment status is '{0}': {1}
+ {0} - deployment status name, {1} - deployment status text
+
diff --git a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.cs.xlf b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.cs.xlf
index d678a6f4b63a..56dc331335f9 100644
--- a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.cs.xlf
+++ b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.cs.xlf
@@ -224,6 +224,26 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
+
+ Deployment status is '{0}'.
+ Deployment status is '{0}'.
+ {0} - deployment status name
+
+
+ Deployment status is '{0}': {1}
+ Deployment status is '{0}': {1}
+ {0} - deployment status name, {1} - deployment status text
+
+
+ Deployment status URL '{0}' is missing or invalid
+ Deployment status URL '{0}' is missing or invalid
+ {0} - deployment polling URL
+
+
+ Polling for deployment status...
+ Polling for deployment status...
+
+
Generating Entity Framework SQL Scripts...
Generují se skripty SQL Entity Framework...
@@ -334,6 +354,51 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
Neplatný příkaz ({0}) pro zadaný zdroj ({1}) a cíl ({2}).
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ {0} - publish URL, {1} - HTTP response status code
+
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ {0} - publish URL, {1} - HTTP response status code, {2} - response body as text
+
+
+ Failed to retrieve publish credentials.
+ Failed to retrieve publish credentials.
+
+
+
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ {0} - file to publish, {1} - publish URL, {2} - deployment response status code, {3} - deployment logs URL
+
+
+ File to publish '{0}' was not found or is not accessible.
+ File to publish '{0}' was not found or is not accessible.
+ {0} - file to publish
+
+
+ PublishUrl '{0}' is missing or has an invalid value.
+ PublishUrl '{0}' is missing or has an invalid value.
+ {0} - publish URL
+
+
+ Publishing '{0}' to '{1}'...
+ Publishing '{0}' to '{1}'...
+ {0} - file to publish, {1} - publish URL
+
+
+ OneDeploy deployment succeeded.
+ OneDeploy deployment succeeded.
+
+
+
+ File '{0}' uploaded to target instance.
+ File '{0}' uploaded to target instance.
+ {0} - file to publish.
+
Unable to parse the pubxml file {0}.
Soubor pubxml {0} nejde analyzovat.
diff --git a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.de.xlf b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.de.xlf
index 30ac55421055..c4413d928128 100644
--- a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.de.xlf
+++ b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.de.xlf
@@ -224,6 +224,26 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
+
+ Deployment status is '{0}'.
+ Deployment status is '{0}'.
+ {0} - deployment status name
+
+
+ Deployment status is '{0}': {1}
+ Deployment status is '{0}': {1}
+ {0} - deployment status name, {1} - deployment status text
+
+
+ Deployment status URL '{0}' is missing or invalid
+ Deployment status URL '{0}' is missing or invalid
+ {0} - deployment polling URL
+
+
+ Polling for deployment status...
+ Polling for deployment status...
+
+
Generating Entity Framework SQL Scripts...
Entity Framework SQL-Skripts werden generiert...
@@ -334,6 +354,51 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
Ungültiges Verb ({0}) für die angegebene Quelle ({1}) und das Ziel ({2}).
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ {0} - publish URL, {1} - HTTP response status code
+
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ {0} - publish URL, {1} - HTTP response status code, {2} - response body as text
+
+
+ Failed to retrieve publish credentials.
+ Failed to retrieve publish credentials.
+
+
+
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ {0} - file to publish, {1} - publish URL, {2} - deployment response status code, {3} - deployment logs URL
+
+
+ File to publish '{0}' was not found or is not accessible.
+ File to publish '{0}' was not found or is not accessible.
+ {0} - file to publish
+
+
+ PublishUrl '{0}' is missing or has an invalid value.
+ PublishUrl '{0}' is missing or has an invalid value.
+ {0} - publish URL
+
+
+ Publishing '{0}' to '{1}'...
+ Publishing '{0}' to '{1}'...
+ {0} - file to publish, {1} - publish URL
+
+
+ OneDeploy deployment succeeded.
+ OneDeploy deployment succeeded.
+
+
+
+ File '{0}' uploaded to target instance.
+ File '{0}' uploaded to target instance.
+ {0} - file to publish.
+
Unable to parse the pubxml file {0}.
Die PUBXML-Datei "{0}" kann nicht analysiert werden.
diff --git a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.es.xlf b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.es.xlf
index 273ccf4478d5..ae528678ccea 100644
--- a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.es.xlf
+++ b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.es.xlf
@@ -224,6 +224,26 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
Vínculo redireccionable: http://go.microsoft.com/fwlink/?LinkId=246068
+
+ Deployment status is '{0}'.
+ Deployment status is '{0}'.
+ {0} - deployment status name
+
+
+ Deployment status is '{0}': {1}
+ Deployment status is '{0}': {1}
+ {0} - deployment status name, {1} - deployment status text
+
+
+ Deployment status URL '{0}' is missing or invalid
+ Deployment status URL '{0}' is missing or invalid
+ {0} - deployment polling URL
+
+
+ Polling for deployment status...
+ Polling for deployment status...
+
+
Generating Entity Framework SQL Scripts...
Generando scripts SQL de Entity Framework...
@@ -334,6 +354,51 @@ Vínculo redireccionable: http://go.microsoft.com/fwlink/?LinkId=246068
verb({0}) no válido para el origen ({1}) y el destino ({2}) proporcionados.
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ {0} - publish URL, {1} - HTTP response status code
+
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ {0} - publish URL, {1} - HTTP response status code, {2} - response body as text
+
+
+ Failed to retrieve publish credentials.
+ Failed to retrieve publish credentials.
+
+
+
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ {0} - file to publish, {1} - publish URL, {2} - deployment response status code, {3} - deployment logs URL
+
+
+ File to publish '{0}' was not found or is not accessible.
+ File to publish '{0}' was not found or is not accessible.
+ {0} - file to publish
+
+
+ PublishUrl '{0}' is missing or has an invalid value.
+ PublishUrl '{0}' is missing or has an invalid value.
+ {0} - publish URL
+
+
+ Publishing '{0}' to '{1}'...
+ Publishing '{0}' to '{1}'...
+ {0} - file to publish, {1} - publish URL
+
+
+ OneDeploy deployment succeeded.
+ OneDeploy deployment succeeded.
+
+
+
+ File '{0}' uploaded to target instance.
+ File '{0}' uploaded to target instance.
+ {0} - file to publish.
+
Unable to parse the pubxml file {0}.
No se puede analizar el archivo pubxml {0}.
diff --git a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.fr.xlf b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.fr.xlf
index 474215104337..fea3cb7722af 100644
--- a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.fr.xlf
+++ b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.fr.xlf
@@ -224,6 +224,26 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
FWLink : http://go.microsoft.com/fwlink/?LinkId=246068
+
+ Deployment status is '{0}'.
+ Deployment status is '{0}'.
+ {0} - deployment status name
+
+
+ Deployment status is '{0}': {1}
+ Deployment status is '{0}': {1}
+ {0} - deployment status name, {1} - deployment status text
+
+
+ Deployment status URL '{0}' is missing or invalid
+ Deployment status URL '{0}' is missing or invalid
+ {0} - deployment polling URL
+
+
+ Polling for deployment status...
+ Polling for deployment status...
+
+
Generating Entity Framework SQL Scripts...
Génération de scripts SQL Entity Framework...
@@ -334,6 +354,51 @@ FWLink : http://go.microsoft.com/fwlink/?LinkId=246068
Verbe incorrect ({0}) pour la source ({1}) fournie et la destination ({2}).
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ {0} - publish URL, {1} - HTTP response status code
+
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ {0} - publish URL, {1} - HTTP response status code, {2} - response body as text
+
+
+ Failed to retrieve publish credentials.
+ Failed to retrieve publish credentials.
+
+
+
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ {0} - file to publish, {1} - publish URL, {2} - deployment response status code, {3} - deployment logs URL
+
+
+ File to publish '{0}' was not found or is not accessible.
+ File to publish '{0}' was not found or is not accessible.
+ {0} - file to publish
+
+
+ PublishUrl '{0}' is missing or has an invalid value.
+ PublishUrl '{0}' is missing or has an invalid value.
+ {0} - publish URL
+
+
+ Publishing '{0}' to '{1}'...
+ Publishing '{0}' to '{1}'...
+ {0} - file to publish, {1} - publish URL
+
+
+ OneDeploy deployment succeeded.
+ OneDeploy deployment succeeded.
+
+
+
+ File '{0}' uploaded to target instance.
+ File '{0}' uploaded to target instance.
+ {0} - file to publish.
+
Unable to parse the pubxml file {0}.
Impossible d’analyser le fichier pubxml {0}.
diff --git a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.it.xlf b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.it.xlf
index 80525ff3cd19..c31079265702 100644
--- a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.it.xlf
+++ b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.it.xlf
@@ -224,6 +224,26 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
+
+ Deployment status is '{0}'.
+ Deployment status is '{0}'.
+ {0} - deployment status name
+
+
+ Deployment status is '{0}': {1}
+ Deployment status is '{0}': {1}
+ {0} - deployment status name, {1} - deployment status text
+
+
+ Deployment status URL '{0}' is missing or invalid
+ Deployment status URL '{0}' is missing or invalid
+ {0} - deployment polling URL
+
+
+ Polling for deployment status...
+ Polling for deployment status...
+
+
Generating Entity Framework SQL Scripts...
Generazione dello script SQL di Entity Framework...
@@ -334,6 +354,51 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
Verbo ({0}) non valido per l'origine ({1}) e la destinazione ({2}) specificate.
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ {0} - publish URL, {1} - HTTP response status code
+
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ {0} - publish URL, {1} - HTTP response status code, {2} - response body as text
+
+
+ Failed to retrieve publish credentials.
+ Failed to retrieve publish credentials.
+
+
+
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ {0} - file to publish, {1} - publish URL, {2} - deployment response status code, {3} - deployment logs URL
+
+
+ File to publish '{0}' was not found or is not accessible.
+ File to publish '{0}' was not found or is not accessible.
+ {0} - file to publish
+
+
+ PublishUrl '{0}' is missing or has an invalid value.
+ PublishUrl '{0}' is missing or has an invalid value.
+ {0} - publish URL
+
+
+ Publishing '{0}' to '{1}'...
+ Publishing '{0}' to '{1}'...
+ {0} - file to publish, {1} - publish URL
+
+
+ OneDeploy deployment succeeded.
+ OneDeploy deployment succeeded.
+
+
+
+ File '{0}' uploaded to target instance.
+ File '{0}' uploaded to target instance.
+ {0} - file to publish.
+
Unable to parse the pubxml file {0}.
Non è possibile analizzare il file pubxml {0}.
diff --git a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.ja.xlf b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.ja.xlf
index 9e6a31245659..5dca3cb12919 100644
--- a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.ja.xlf
+++ b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.ja.xlf
@@ -224,6 +224,26 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
+
+ Deployment status is '{0}'.
+ Deployment status is '{0}'.
+ {0} - deployment status name
+
+
+ Deployment status is '{0}': {1}
+ Deployment status is '{0}': {1}
+ {0} - deployment status name, {1} - deployment status text
+
+
+ Deployment status URL '{0}' is missing or invalid
+ Deployment status URL '{0}' is missing or invalid
+ {0} - deployment polling URL
+
+
+ Polling for deployment status...
+ Polling for deployment status...
+
+
Generating Entity Framework SQL Scripts...
Entity Framework SQL スクリプトを生成しています...
@@ -334,6 +354,51 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
指定された配置元 ({1}) および配置先 ({2}) の動詞 ({0}) が無効です。
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ {0} - publish URL, {1} - HTTP response status code
+
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ {0} - publish URL, {1} - HTTP response status code, {2} - response body as text
+
+
+ Failed to retrieve publish credentials.
+ Failed to retrieve publish credentials.
+
+
+
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ {0} - file to publish, {1} - publish URL, {2} - deployment response status code, {3} - deployment logs URL
+
+
+ File to publish '{0}' was not found or is not accessible.
+ File to publish '{0}' was not found or is not accessible.
+ {0} - file to publish
+
+
+ PublishUrl '{0}' is missing or has an invalid value.
+ PublishUrl '{0}' is missing or has an invalid value.
+ {0} - publish URL
+
+
+ Publishing '{0}' to '{1}'...
+ Publishing '{0}' to '{1}'...
+ {0} - file to publish, {1} - publish URL
+
+
+ OneDeploy deployment succeeded.
+ OneDeploy deployment succeeded.
+
+
+
+ File '{0}' uploaded to target instance.
+ File '{0}' uploaded to target instance.
+ {0} - file to publish.
+
Unable to parse the pubxml file {0}.
pubxml ファイル {0} を解析できません。
diff --git a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.ko.xlf b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.ko.xlf
index 69c0f3f458fc..ce92d6066e47 100644
--- a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.ko.xlf
+++ b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.ko.xlf
@@ -224,6 +224,26 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
+
+ Deployment status is '{0}'.
+ Deployment status is '{0}'.
+ {0} - deployment status name
+
+
+ Deployment status is '{0}': {1}
+ Deployment status is '{0}': {1}
+ {0} - deployment status name, {1} - deployment status text
+
+
+ Deployment status URL '{0}' is missing or invalid
+ Deployment status URL '{0}' is missing or invalid
+ {0} - deployment polling URL
+
+
+ Polling for deployment status...
+ Polling for deployment status...
+
+
Generating Entity Framework SQL Scripts...
Entity Framework SQL 스크립트 생성 중...
@@ -334,6 +354,51 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
제공된 소스({1})와 대상({2})에 대해 잘못된 동사({0})입니다.
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ {0} - publish URL, {1} - HTTP response status code
+
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ {0} - publish URL, {1} - HTTP response status code, {2} - response body as text
+
+
+ Failed to retrieve publish credentials.
+ Failed to retrieve publish credentials.
+
+
+
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ {0} - file to publish, {1} - publish URL, {2} - deployment response status code, {3} - deployment logs URL
+
+
+ File to publish '{0}' was not found or is not accessible.
+ File to publish '{0}' was not found or is not accessible.
+ {0} - file to publish
+
+
+ PublishUrl '{0}' is missing or has an invalid value.
+ PublishUrl '{0}' is missing or has an invalid value.
+ {0} - publish URL
+
+
+ Publishing '{0}' to '{1}'...
+ Publishing '{0}' to '{1}'...
+ {0} - file to publish, {1} - publish URL
+
+
+ OneDeploy deployment succeeded.
+ OneDeploy deployment succeeded.
+
+
+
+ File '{0}' uploaded to target instance.
+ File '{0}' uploaded to target instance.
+ {0} - file to publish.
+
Unable to parse the pubxml file {0}.
pubxml 파일 {0}을(를) 구문 분석할 수 없습니다.
diff --git a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.pl.xlf b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.pl.xlf
index 12bb60fd137b..8218f58524a3 100644
--- a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.pl.xlf
+++ b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.pl.xlf
@@ -224,6 +224,26 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
+
+ Deployment status is '{0}'.
+ Deployment status is '{0}'.
+ {0} - deployment status name
+
+
+ Deployment status is '{0}': {1}
+ Deployment status is '{0}': {1}
+ {0} - deployment status name, {1} - deployment status text
+
+
+ Deployment status URL '{0}' is missing or invalid
+ Deployment status URL '{0}' is missing or invalid
+ {0} - deployment polling URL
+
+
+ Polling for deployment status...
+ Polling for deployment status...
+
+
Generating Entity Framework SQL Scripts...
Trwa generowanie skryptów SQL Entity Framework...
@@ -334,6 +354,51 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
Nieprawidłowe zlecenie ({0}) dla podanego źródła ({1}) i elementu docelowego ({2}).
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ {0} - publish URL, {1} - HTTP response status code
+
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ {0} - publish URL, {1} - HTTP response status code, {2} - response body as text
+
+
+ Failed to retrieve publish credentials.
+ Failed to retrieve publish credentials.
+
+
+
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ {0} - file to publish, {1} - publish URL, {2} - deployment response status code, {3} - deployment logs URL
+
+
+ File to publish '{0}' was not found or is not accessible.
+ File to publish '{0}' was not found or is not accessible.
+ {0} - file to publish
+
+
+ PublishUrl '{0}' is missing or has an invalid value.
+ PublishUrl '{0}' is missing or has an invalid value.
+ {0} - publish URL
+
+
+ Publishing '{0}' to '{1}'...
+ Publishing '{0}' to '{1}'...
+ {0} - file to publish, {1} - publish URL
+
+
+ OneDeploy deployment succeeded.
+ OneDeploy deployment succeeded.
+
+
+
+ File '{0}' uploaded to target instance.
+ File '{0}' uploaded to target instance.
+ {0} - file to publish.
+
Unable to parse the pubxml file {0}.
Nie można przeanalizować pliku pubxml {0}.
diff --git a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.pt-BR.xlf b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.pt-BR.xlf
index 5dfc66eebbb8..8a7ffbfd0bf3 100644
--- a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.pt-BR.xlf
+++ b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.pt-BR.xlf
@@ -224,6 +224,26 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
+
+ Deployment status is '{0}'.
+ Deployment status is '{0}'.
+ {0} - deployment status name
+
+
+ Deployment status is '{0}': {1}
+ Deployment status is '{0}': {1}
+ {0} - deployment status name, {1} - deployment status text
+
+
+ Deployment status URL '{0}' is missing or invalid
+ Deployment status URL '{0}' is missing or invalid
+ {0} - deployment polling URL
+
+
+ Polling for deployment status...
+ Polling for deployment status...
+
+
Generating Entity Framework SQL Scripts...
Gerando Scripts SQL do Entity Framework...
@@ -334,6 +354,51 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
Verbo inválido ({0}) para origem ({1}) e destino ({2}) fornecidos.
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ {0} - publish URL, {1} - HTTP response status code
+
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ {0} - publish URL, {1} - HTTP response status code, {2} - response body as text
+
+
+ Failed to retrieve publish credentials.
+ Failed to retrieve publish credentials.
+
+
+
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ {0} - file to publish, {1} - publish URL, {2} - deployment response status code, {3} - deployment logs URL
+
+
+ File to publish '{0}' was not found or is not accessible.
+ File to publish '{0}' was not found or is not accessible.
+ {0} - file to publish
+
+
+ PublishUrl '{0}' is missing or has an invalid value.
+ PublishUrl '{0}' is missing or has an invalid value.
+ {0} - publish URL
+
+
+ Publishing '{0}' to '{1}'...
+ Publishing '{0}' to '{1}'...
+ {0} - file to publish, {1} - publish URL
+
+
+ OneDeploy deployment succeeded.
+ OneDeploy deployment succeeded.
+
+
+
+ File '{0}' uploaded to target instance.
+ File '{0}' uploaded to target instance.
+ {0} - file to publish.
+
Unable to parse the pubxml file {0}.
Não é possível analisar o arquivo pubxml {0}.
diff --git a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.ru.xlf b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.ru.xlf
index e839b3a1965f..8c22509aa93f 100644
--- a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.ru.xlf
+++ b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.ru.xlf
@@ -224,6 +224,26 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
FWLink: http://go.microsoft.com/fwlink/?LinkId=246068.
+
+ Deployment status is '{0}'.
+ Deployment status is '{0}'.
+ {0} - deployment status name
+
+
+ Deployment status is '{0}': {1}
+ Deployment status is '{0}': {1}
+ {0} - deployment status name, {1} - deployment status text
+
+
+ Deployment status URL '{0}' is missing or invalid
+ Deployment status URL '{0}' is missing or invalid
+ {0} - deployment polling URL
+
+
+ Polling for deployment status...
+ Polling for deployment status...
+
+
Generating Entity Framework SQL Scripts...
Создание сценариев SQL для Entity Framework…
@@ -334,6 +354,51 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068.
Недопустимая команда ({0}) для указанных источника ({1}) и назначения ({2}).
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ {0} - publish URL, {1} - HTTP response status code
+
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ {0} - publish URL, {1} - HTTP response status code, {2} - response body as text
+
+
+ Failed to retrieve publish credentials.
+ Failed to retrieve publish credentials.
+
+
+
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ {0} - file to publish, {1} - publish URL, {2} - deployment response status code, {3} - deployment logs URL
+
+
+ File to publish '{0}' was not found or is not accessible.
+ File to publish '{0}' was not found or is not accessible.
+ {0} - file to publish
+
+
+ PublishUrl '{0}' is missing or has an invalid value.
+ PublishUrl '{0}' is missing or has an invalid value.
+ {0} - publish URL
+
+
+ Publishing '{0}' to '{1}'...
+ Publishing '{0}' to '{1}'...
+ {0} - file to publish, {1} - publish URL
+
+
+ OneDeploy deployment succeeded.
+ OneDeploy deployment succeeded.
+
+
+
+ File '{0}' uploaded to target instance.
+ File '{0}' uploaded to target instance.
+ {0} - file to publish.
+
Unable to parse the pubxml file {0}.
Не удалось проанализировать PUBXML-файл {0}.
diff --git a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.tr.xlf b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.tr.xlf
index a5fdc7161c84..0d6c30f5602a 100644
--- a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.tr.xlf
+++ b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.tr.xlf
@@ -224,6 +224,26 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
+
+ Deployment status is '{0}'.
+ Deployment status is '{0}'.
+ {0} - deployment status name
+
+
+ Deployment status is '{0}': {1}
+ Deployment status is '{0}': {1}
+ {0} - deployment status name, {1} - deployment status text
+
+
+ Deployment status URL '{0}' is missing or invalid
+ Deployment status URL '{0}' is missing or invalid
+ {0} - deployment polling URL
+
+
+ Polling for deployment status...
+ Polling for deployment status...
+
+
Generating Entity Framework SQL Scripts...
Entity Framework SQL Betikleri oluşturuluyor...
@@ -334,6 +354,51 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
Sağlanan kaynak ({1}) ve hedef ({2}) için geçersiz eylem ({0}).
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ {0} - publish URL, {1} - HTTP response status code
+
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ {0} - publish URL, {1} - HTTP response status code, {2} - response body as text
+
+
+ Failed to retrieve publish credentials.
+ Failed to retrieve publish credentials.
+
+
+
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ {0} - file to publish, {1} - publish URL, {2} - deployment response status code, {3} - deployment logs URL
+
+
+ File to publish '{0}' was not found or is not accessible.
+ File to publish '{0}' was not found or is not accessible.
+ {0} - file to publish
+
+
+ PublishUrl '{0}' is missing or has an invalid value.
+ PublishUrl '{0}' is missing or has an invalid value.
+ {0} - publish URL
+
+
+ Publishing '{0}' to '{1}'...
+ Publishing '{0}' to '{1}'...
+ {0} - file to publish, {1} - publish URL
+
+
+ OneDeploy deployment succeeded.
+ OneDeploy deployment succeeded.
+
+
+
+ File '{0}' uploaded to target instance.
+ File '{0}' uploaded to target instance.
+ {0} - file to publish.
+
Unable to parse the pubxml file {0}.
{0} pubxml dosyası ayrıştırılamadı.
diff --git a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.zh-Hans.xlf b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.zh-Hans.xlf
index 42b9ab03d9b5..6eb0a8098a06 100644
--- a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.zh-Hans.xlf
+++ b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.zh-Hans.xlf
@@ -224,6 +224,26 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
+
+ Deployment status is '{0}'.
+ Deployment status is '{0}'.
+ {0} - deployment status name
+
+
+ Deployment status is '{0}': {1}
+ Deployment status is '{0}': {1}
+ {0} - deployment status name, {1} - deployment status text
+
+
+ Deployment status URL '{0}' is missing or invalid
+ Deployment status URL '{0}' is missing or invalid
+ {0} - deployment polling URL
+
+
+ Polling for deployment status...
+ Polling for deployment status...
+
+
Generating Entity Framework SQL Scripts...
正在生成实体框架 SQL 脚本...
@@ -334,6 +354,51 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
所提供的源({1})和目标({2})的谓词({0})无效。
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ {0} - publish URL, {1} - HTTP response status code
+
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ {0} - publish URL, {1} - HTTP response status code, {2} - response body as text
+
+
+ Failed to retrieve publish credentials.
+ Failed to retrieve publish credentials.
+
+
+
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ {0} - file to publish, {1} - publish URL, {2} - deployment response status code, {3} - deployment logs URL
+
+
+ File to publish '{0}' was not found or is not accessible.
+ File to publish '{0}' was not found or is not accessible.
+ {0} - file to publish
+
+
+ PublishUrl '{0}' is missing or has an invalid value.
+ PublishUrl '{0}' is missing or has an invalid value.
+ {0} - publish URL
+
+
+ Publishing '{0}' to '{1}'...
+ Publishing '{0}' to '{1}'...
+ {0} - file to publish, {1} - publish URL
+
+
+ OneDeploy deployment succeeded.
+ OneDeploy deployment succeeded.
+
+
+
+ File '{0}' uploaded to target instance.
+ File '{0}' uploaded to target instance.
+ {0} - file to publish.
+
Unable to parse the pubxml file {0}.
无法分析 pubxml 文件 {0}。
diff --git a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.zh-Hant.xlf b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.zh-Hant.xlf
index db5b2441de84..96c6046aa817 100644
--- a/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.zh-Hant.xlf
+++ b/src/WebSdk/Publish/Tasks/Properties/xlf/Resources.zh-Hant.xlf
@@ -224,6 +224,26 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
+
+ Deployment status is '{0}'.
+ Deployment status is '{0}'.
+ {0} - deployment status name
+
+
+ Deployment status is '{0}': {1}
+ Deployment status is '{0}': {1}
+ {0} - deployment status name, {1} - deployment status text
+
+
+ Deployment status URL '{0}' is missing or invalid
+ Deployment status URL '{0}' is missing or invalid
+ {0} - deployment polling URL
+
+
+ Polling for deployment status...
+ Polling for deployment status...
+
+
Generating Entity Framework SQL Scripts...
正在產生 Entity Framework SQL 指令碼...
@@ -334,6 +354,51 @@ FWLink: http://go.microsoft.com/fwlink/?LinkId=246068
所提供來源 ({1}) 及目的地 ({2}) 的動詞 ({0}) 無效
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}'.
+ {0} - publish URL, {1} - HTTP response status code
+
+
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ OneDeploy attempt to publish file through '{0}' failed with HTTP status code '{1}': {2}.
+ {0} - publish URL, {1} - HTTP response status code, {2} - response body as text
+
+
+ Failed to retrieve publish credentials.
+ Failed to retrieve publish credentials.
+
+
+
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ OneDeploy attempt to publish file '{0}' through '{1}' failed with status code '{2}'. See the logs at '{3}'.
+ {0} - file to publish, {1} - publish URL, {2} - deployment response status code, {3} - deployment logs URL
+
+
+ File to publish '{0}' was not found or is not accessible.
+ File to publish '{0}' was not found or is not accessible.
+ {0} - file to publish
+
+
+ PublishUrl '{0}' is missing or has an invalid value.
+ PublishUrl '{0}' is missing or has an invalid value.
+ {0} - publish URL
+
+
+ Publishing '{0}' to '{1}'...
+ Publishing '{0}' to '{1}'...
+ {0} - file to publish, {1} - publish URL
+
+
+ OneDeploy deployment succeeded.
+ OneDeploy deployment succeeded.
+
+
+
+ File '{0}' uploaded to target instance.
+ File '{0}' uploaded to target instance.
+ {0} - file to publish.
+
Unable to parse the pubxml file {0}.
無法剖析 pubxml 檔案 {0}。
diff --git a/src/WebSdk/Publish/Tasks/Tasks/Http/DefaultHttpClient.cs b/src/WebSdk/Publish/Tasks/Tasks/Http/DefaultHttpClient.cs
new file mode 100644
index 000000000000..2d251c18d954
--- /dev/null
+++ b/src/WebSdk/Publish/Tasks/Tasks/Http/DefaultHttpClient.cs
@@ -0,0 +1,42 @@
+// 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.Http;
+using System.Net.Http.Headers;
+
+namespace Microsoft.NET.Sdk.Publish.Tasks;
+
+internal class DefaultHttpClient : IHttpClient, IDisposable
+{
+ private readonly HttpClient _httpClient = new()
+ {
+ Timeout = Timeout.InfiniteTimeSpan
+ };
+
+ ///
+ public HttpRequestHeaders DefaultRequestHeaders => _httpClient.DefaultRequestHeaders;
+
+ ///
+ public Task PostAsync(Uri uri, StreamContent content)
+ {
+ return _httpClient.PostAsync(uri, content);
+ }
+
+ ///
+ public Task GetAsync(Uri uri, CancellationToken cancellationToken)
+ {
+ return _httpClient.GetAsync(uri, cancellationToken);
+ }
+
+ ///
+ public Task PutAsync(Uri uri, StreamContent content, CancellationToken cancellationToken)
+ {
+ return _httpClient.PutAsync(uri, content, cancellationToken);
+ }
+
+ ///
+ public void Dispose()
+ {
+ _httpClient.Dispose();
+ }
+}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/Http/HttpClientExtensions.cs b/src/WebSdk/Publish/Tasks/Tasks/Http/HttpClientExtensions.cs
new file mode 100644
index 000000000000..5565638afd51
--- /dev/null
+++ b/src/WebSdk/Publish/Tasks/Tasks/Http/HttpClientExtensions.cs
@@ -0,0 +1,328 @@
+// 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 System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text.Json;
+
+namespace Microsoft.NET.Sdk.Publish.Tasks;
+
+///
+/// Extension methods for and related types.
+///
+internal static class HttpClientExtensions
+{
+ private static readonly string s_azureADUserName = Guid.Empty.ToString();
+ private static readonly JsonSerializerOptions s_defaultSerializerOptions = new()
+ {
+ AllowTrailingCommas = true,
+ ReadCommentHandling = JsonCommentHandling.Skip
+ };
+
+ private const string BearerAuthenticationScheme = "Bearer";
+ private const string BasicAuthenticationScheme = "Basic";
+
+ ///
+ /// Sends an HTTP POST request.
+ ///
+ /// uri to send the request to
+ /// user name
+ /// user password
+ /// content type header value
+ /// 'User-Agent' header value
+ /// encoding
+ /// message payload
+ /// HTTP response
+ public static async Task PostRequestAsync(
+ this IHttpClient client,
+ Uri uri,
+ string username,
+ string password,
+ string contentType,
+ string userAgent,
+ Encoding encoding,
+ Stream messageBody)
+ {
+ if (client is null)
+ {
+ return null;
+ }
+
+ AddAuthenticationHeader(username, password, client);
+ client.DefaultRequestHeaders.Add("User-Agent", userAgent);
+
+ StreamContent content = new(messageBody ?? new MemoryStream())
+ {
+ Headers =
+ {
+ ContentType = new MediaTypeHeaderValue(contentType)
+ {
+ CharSet = encoding.WebName
+ },
+ ContentEncoding =
+ {
+ encoding.WebName
+ }
+ }
+ };
+
+ try
+ {
+ HttpResponseMessage responseMessage = await client.PostAsync(uri, content);
+ return new HttpResponseMessageWrapper(responseMessage);
+ }
+ catch (TaskCanceledException)
+ {
+ return new HttpResponseMessageForStatusCode(HttpStatusCode.RequestTimeout);
+ }
+ }
+
+ ///
+ /// Sends an HTTP PUT request.
+ ///
+ /// uri to send the request to
+ /// user name
+ /// user password
+ /// content type header value
+ /// 'User-Agent' header value
+ /// encoding
+ /// message payload
+ /// HTTP response
+ public static async Task PutRequestAsync(
+ this IHttpClient client,
+ Uri uri,
+ string username,
+ string password,
+ string contentType,
+ string userAgent,
+ string fileName,
+ Encoding encoding,
+ Stream messageBody,
+ CancellationToken cancellationToken)
+ {
+ if (client is null || cancellationToken.IsCancellationRequested)
+ {
+ return null;
+ }
+
+ AddAuthenticationHeader(username, password, client);
+ client.DefaultRequestHeaders.Add("User-Agent", userAgent);
+
+ StreamContent content = new(messageBody ?? new MemoryStream())
+ {
+ Headers =
+ {
+ ContentType = new MediaTypeHeaderValue(contentType),
+ ContentEncoding =
+ {
+ encoding.WebName
+ },
+ ContentDisposition = new ContentDispositionHeaderValue("attachment")
+ {
+ FileName = fileName
+ }
+ }
+ };
+
+ try
+ {
+ HttpResponseMessage responseMessage = await client.PutAsync(uri, content, cancellationToken);
+ return new HttpResponseMessageWrapper(responseMessage);
+ }
+ catch (TaskCanceledException)
+ {
+ return new HttpResponseMessageForStatusCode(HttpStatusCode.RequestTimeout);
+ }
+ }
+
+ ///
+ /// Sends an HTTP GET request.
+ ///
+ /// uri to send the request to
+ /// user name
+ /// user password
+ /// 'User-Agent' header value
+ ///
+ /// HTTP response
+ public static async Task GetRequestAsync(
+ this IHttpClient client,
+ Uri uri,
+ string username,
+ string password,
+ string userAgent,
+ CancellationToken cancellationToken)
+ {
+ if (client is null || cancellationToken.IsCancellationRequested)
+ {
+ return null;
+ }
+
+ AddAuthenticationHeader(username, password, client);
+ client.DefaultRequestHeaders.Add("User-Agent", userAgent);
+
+ try
+ {
+ HttpResponseMessage responseMessage = await client.GetAsync(uri, cancellationToken);
+ return new HttpResponseMessageWrapper(responseMessage);
+ }
+ catch (TaskCanceledException)
+ {
+ return new HttpResponseMessageForStatusCode(HttpStatusCode.RequestTimeout);
+ }
+ }
+
+ ///
+ /// Sends HTTP GET request a maximum attempts. It will retry while
+ /// request is not OK/Accepted or maximum number of retries has been reached.
+ ///
+ /// expected type of response object
+ /// URL to send requests to
+ /// user name
+ /// user password
+ /// 'User-Agent' header value
+ /// maximum number of attempts
+ /// time to wait between attempts; usually in seconds
+ /// cancellation token
+ /// response of given type; default value if response status code is not of success
+ public static async Task RetryGetRequestAsync(
+ this IHttpClient client,
+ string url,
+ string username,
+ string password,
+ string userAgent,
+ int retries,
+ TimeSpan delay,
+ CancellationToken cancellationToken)
+ {
+ if (client is null)
+ {
+ return default;
+ }
+
+ // retry GET request
+ IHttpResponse response = null;
+ await RetryTaskAsync(async (ct) =>
+ {
+ response = await client.GetRequestAsync(new Uri(url, UriKind.RelativeOrAbsolute), username, password, userAgent, ct);
+ }, retries, delay, cancellationToken);
+
+ // response is not valid; return default value
+ if (!response.IsResponseSuccessful())
+ {
+ return default;
+ }
+
+ return await response.GetJsonResponseAsync(cancellationToken);
+ }
+
+ ///
+ /// Whether given has a status of
+ /// or
+ ///
+ ///
+ ///
+ public static bool IsResponseSuccessful(this IHttpResponse response)
+ {
+ return response is not null
+ && (response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.Accepted);
+
+ }
+
+ ///
+ /// Reads the body as a string.
+ ///
+ /// cancellation token
+ /// the response body as text
+ public static async Task GetTextResponseAsync(this IHttpResponse response, CancellationToken cancellationToken)
+ {
+ var responseText = string.Empty;
+
+ if (response is null || cancellationToken.IsCancellationRequested)
+ {
+ return responseText;
+ }
+
+ var responseBody = await response.GetResponseBodyAsync();
+ if (responseBody is not null)
+ {
+ var streamReader = new StreamReader(responseBody);
+ responseText = streamReader.ReadToEnd();
+ }
+
+ return responseText;
+ }
+
+ ///
+ /// Attempts to serialize the JSON content into an object of the given type.
+ ///
+ /// type to serialize to
+ /// cancellation token
+ /// object
+ public static async Task GetJsonResponseAsync(this IHttpResponse response, CancellationToken cancellation)
+ {
+ if (response is null || cancellation.IsCancellationRequested)
+ {
+ return default;
+ }
+
+ using var stream = await response.GetResponseBodyAsync();
+ var reader = new StreamReader(stream, Encoding.UTF8);
+
+ return JsonSerializer.Deserialize(reader.ReadToEnd(), s_defaultSerializerOptions);
+ }
+
+ private static void AddAuthenticationHeader(string username, string password, IHttpClient client)
+ {
+ client.DefaultRequestHeaders.Remove("Connection");
+
+ if (!string.Equals(username, s_azureADUserName, StringComparison.Ordinal))
+ {
+ string plainAuth = string.Format("{0}:{1}", username, password);
+ byte[] plainAuthBytes = Encoding.ASCII.GetBytes(plainAuth);
+ string base64 = Convert.ToBase64String(plainAuthBytes);
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(BasicAuthenticationScheme, base64);
+ }
+ else
+ {
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(BearerAuthenticationScheme, password);
+ }
+ }
+
+ ///
+ /// Attempts to run the given function at least 1 time and at most times.
+ ///
+ /// function to run
+ /// maximum number of attempts
+ /// delay between each attempt
+ private static async System.Threading.Tasks.Task RetryTaskAsync(
+ Func func,
+ int retryCount,
+ TimeSpan retryDelay,
+ CancellationToken cancellationToken)
+ {
+ while (true)
+ {
+ try
+ {
+ if (!cancellationToken.IsCancellationRequested)
+ {
+ await func(cancellationToken);
+ }
+
+ return;
+ }
+ catch (Exception)
+ {
+ if (retryCount <= 0)
+ {
+ throw;
+ }
+
+ retryCount--;
+ }
+
+ await System.Threading.Tasks.Task.Delay(retryDelay, cancellationToken);
+ }
+ }
+}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/Http/HttpResponseMessageForStatusCode.cs b/src/WebSdk/Publish/Tasks/Tasks/Http/HttpResponseMessageForStatusCode.cs
new file mode 100644
index 000000000000..e5de396a718d
--- /dev/null
+++ b/src/WebSdk/Publish/Tasks/Tasks/Http/HttpResponseMessageForStatusCode.cs
@@ -0,0 +1,24 @@
+// 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;
+
+namespace Microsoft.NET.Sdk.Publish.Tasks;
+
+internal class HttpResponseMessageForStatusCode(HttpStatusCode statusCode) : IHttpResponse
+{
+ ///
+ public HttpStatusCode StatusCode { get; private set; } = statusCode;
+
+ ///
+ public Task GetResponseBodyAsync()
+ {
+ return System.Threading.Tasks.Task.FromResult(new MemoryStream());
+ }
+
+ ///
+ public IEnumerable GetHeader(string name)
+ {
+ return [];
+ }
+}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/Http/HttpResponseMessageWrapper.cs b/src/WebSdk/Publish/Tasks/Tasks/Http/HttpResponseMessageWrapper.cs
new file mode 100644
index 000000000000..61cc82f02fad
--- /dev/null
+++ b/src/WebSdk/Publish/Tasks/Tasks/Http/HttpResponseMessageWrapper.cs
@@ -0,0 +1,52 @@
+// 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 System.Net.Http;
+
+namespace Microsoft.NET.Sdk.Publish.Tasks;
+
+internal class HttpResponseMessageWrapper : IHttpResponse
+{
+ private readonly HttpResponseMessage _message;
+ private readonly Lazy> _responseBodyTask;
+
+ public HttpResponseMessageWrapper(HttpResponseMessage message)
+ {
+ _message = message;
+ _responseBodyTask = new Lazy>(GetResponseStream);
+
+ StatusCode = message?.StatusCode ?? HttpStatusCode.InternalServerError;
+ }
+
+ ///
+ public HttpStatusCode StatusCode { get; private set; }
+
+ ///
+ public async Task GetResponseBodyAsync()
+ {
+ return _responseBodyTask.Value is not null
+ ? await _responseBodyTask.Value
+ : null;
+ }
+
+ ///
+ public IEnumerable GetHeader(string name)
+ {
+ if (_message is not null
+ && _message.Headers is not null
+ && _message.Headers.TryGetValues(name, out IEnumerable values))
+ {
+ return values;
+ }
+
+ return [];
+ }
+
+ private Task GetResponseStream()
+ {
+ return _message is not null && _message.Content is not null
+ ? _message.Content.ReadAsStreamAsync()
+ : null;
+ }
+}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/Http/IHttpClient.cs b/src/WebSdk/Publish/Tasks/Tasks/Http/IHttpClient.cs
new file mode 100644
index 000000000000..a7e5fe0f458e
--- /dev/null
+++ b/src/WebSdk/Publish/Tasks/Tasks/Http/IHttpClient.cs
@@ -0,0 +1,43 @@
+// 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.Http;
+using System.Net.Http.Headers;
+
+namespace Microsoft.NET.Sdk.Publish.Tasks;
+
+///
+/// Sends HTTP requests.
+///
+public interface IHttpClient
+{
+ ///
+ /// The Headers of a request.
+ ///
+ HttpRequestHeaders DefaultRequestHeaders { get; }
+
+ ///
+ /// Sends an HTTP POST request.
+ ///
+ /// URI to send the request to
+ /// request payload
+ /// response of request
+ Task PostAsync(Uri uri, StreamContent content);
+
+ ///
+ /// Sends an HTTP GET request.
+ ///
+ /// URI to send the request to
+ /// cancellation token
+ /// response of the request
+ Task GetAsync(Uri uri, CancellationToken cancellationToken);
+
+ ///
+ /// Sends an HTTP PUT request.
+ ///
+ /// URI to send the request to
+ /// request payload
+ /// cancellation token
+ /// response of request
+ Task PutAsync(Uri uri, StreamContent content, CancellationToken cancellationToken);
+}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/Http/IHttpResponse.cs b/src/WebSdk/Publish/Tasks/Tasks/Http/IHttpResponse.cs
new file mode 100644
index 000000000000..29ca812209df
--- /dev/null
+++ b/src/WebSdk/Publish/Tasks/Tasks/Http/IHttpResponse.cs
@@ -0,0 +1,30 @@
+// 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;
+
+namespace Microsoft.NET.Sdk.Publish.Tasks;
+
+///
+/// A response to an HTTP request
+///
+internal interface IHttpResponse
+{
+ ///
+ /// Gets the status code the server returned
+ ///
+ HttpStatusCode StatusCode { get; }
+
+ ///
+ /// Gets the body of the response
+ ///
+ Task GetResponseBodyAsync();
+
+ ///
+ /// Gets the value of an HTTP Response header
+ /// with the given name.
+ ///
+ /// header name
+ /// header value(s)
+ IEnumerable GetHeader(string name);
+}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/CreatePackageFile.cs b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/CreatePackageFile.cs
new file mode 100644
index 000000000000..237fc484bc52
--- /dev/null
+++ b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/CreatePackageFile.cs
@@ -0,0 +1,59 @@
+// 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;
+
+namespace Microsoft.NET.Sdk.Publish.Tasks.OneDeploy;
+
+///
+/// A task that creates a package file for the content in a given path.
+///
+public class CreatePackageFile : Task
+{
+ private readonly IFilePackager _filePackager;
+
+ public CreatePackageFile()
+ {
+ _filePackager = new ZipFilePackager();
+ }
+
+ // Test constructor
+ internal CreatePackageFile(IFilePackager filePackager)
+ {
+ _filePackager = filePackager;
+ }
+
+ [Required]
+ public string ContentToPackage { get; set; }
+
+ [Required]
+ public string ProjectName { get; set; }
+
+ [Required]
+ public string IntermediateTempPath { get; set; }
+
+ [Output]
+ public string CreatedPackageFilePath { get; set; }
+
+ ///
+ public override bool Execute()
+ {
+ if (string.IsNullOrEmpty(ContentToPackage)
+ || string.IsNullOrEmpty(ProjectName)
+ || string.IsNullOrEmpty(IntermediateTempPath))
+ {
+ return false;
+ }
+
+ var packageFileName = $"{ProjectName}-{DateTime.Now:yyyyMMddHHmmssFFF}{_filePackager.Extension}";
+ var packageFilePath = Path.Combine(IntermediateTempPath, packageFileName);
+
+ // package content
+ var packageFileTask = _filePackager.CreatePackageAsync(ContentToPackage, packageFilePath, CancellationToken.None);
+ packageFileTask.Wait();
+
+ CreatedPackageFilePath = packageFileTask.Result ? packageFilePath : string.Empty;
+
+ return !string.IsNullOrEmpty(CreatedPackageFilePath);
+ }
+}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/DeploymentResponse.cs b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/DeploymentResponse.cs
new file mode 100644
index 000000000000..c750d9867685
--- /dev/null
+++ b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/DeploymentResponse.cs
@@ -0,0 +1,121 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text.Json.Serialization;
+using Microsoft.NET.Sdk.Publish.Tasks.Properties;
+
+namespace Microsoft.NET.Sdk.Publish.Tasks.OneDeploy;
+
+///
+/// Represents the JSON response of a deployment operation.
+///
+internal class DeploymentResponse
+{
+ internal static readonly DeploymentResponse s_unknownResponse = new()
+ {
+ Status = DeploymentStatus.Unknown,
+ };
+
+ [JsonPropertyName("id")]
+ public string Id { get; set; }
+
+ [JsonPropertyName("end_time")]
+ public string EndTime { get; set; }
+
+ [JsonPropertyName("log_url")]
+ public string LogUrl { get; set; }
+
+ [JsonPropertyName("message")]
+ public string Message { get; set; }
+
+ [JsonPropertyName("status")]
+ public DeploymentStatus? Status { get; set; } = DeploymentStatus.Unknown;
+
+ [JsonPropertyName("site_name")]
+ public string SiteName { get; set; }
+
+ [JsonPropertyName("status_text")]
+ public string StatusText { get; set; }
+
+ [JsonPropertyName("start_time")]
+ public string StartTime { get; set; }
+
+ [JsonPropertyName("url")]
+ public string Url { get; set; }
+
+ ///
+ public override string ToString()
+ {
+ return string.IsNullOrEmpty(StatusText)
+ ? string.Format(Resources.DeploymentStatus, Status)
+ : string.Format(Resources.DeploymentStatusWithText, Status, StatusText);
+ }
+
+}
+
+///
+/// Extension methods for .
+///
+internal static class DeploymentResponseExtensions
+{
+ internal static bool IsSuccessfulResponse(this DeploymentResponse response)
+ {
+ return response is not null
+ && response.Status is not null
+ && response.Status.Value.IsSuccessfulStatus();
+ }
+
+ internal static bool IsFailedResponse(this DeploymentResponse response)
+ {
+ return response is null
+ || response.Status is null
+ || response.Status.Value.IsFailedStatus();
+ }
+
+ public static string GetLogUrlWithId(this DeploymentResponse deploymentResponse)
+ {
+ if (deploymentResponse is null
+ || string.IsNullOrEmpty(deploymentResponse.LogUrl)
+ || string.IsNullOrEmpty(deploymentResponse.Id))
+ {
+ return deploymentResponse?.LogUrl;
+ }
+
+ try
+ {
+ Uri logUrl = new(deploymentResponse.LogUrl);
+ string pathAndQuery = logUrl.PathAndQuery;
+
+ // try to replace '../latest/log' with '../{deploymentResponse.Id}/log'
+ if (!string.IsNullOrEmpty(pathAndQuery))
+ {
+ string[] pathAndQueryParts = pathAndQuery.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
+ string[] pathWithIdParts = new string[pathAndQueryParts.Length];
+
+ for (int i = pathAndQueryParts.Length - 1; i >= 0; i--)
+ {
+ if (string.Equals("latest", pathAndQueryParts[i], StringComparison.Ordinal))
+ {
+ pathWithIdParts[i] = deploymentResponse.Id;
+ continue;
+ }
+
+ pathWithIdParts[i] = pathAndQueryParts[i].Trim();
+ }
+
+ return new UriBuilder()
+ {
+ Scheme = logUrl.Scheme,
+ Host = logUrl.Host,
+ Path = string.Join("/", pathWithIdParts)
+ }.ToString();
+ }
+ }
+ catch
+ {
+ // do nothing
+ }
+
+ return deploymentResponse.LogUrl;
+ }
+}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/DeploymentStatus.cs b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/DeploymentStatus.cs
new file mode 100644
index 000000000000..c3485d2d6a60
--- /dev/null
+++ b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/DeploymentStatus.cs
@@ -0,0 +1,56 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.NET.Sdk.Publish.Tasks.OneDeploy;
+
+///
+/// Deployment operation status codes
+///
+public enum DeploymentStatus
+{
+ Unknown = -10,
+ Cancelled = -1,
+ Pending = 0,
+ Building = 1,
+ Deploying = 2,
+ Failed = 3,
+ Success = 4,
+ Conflict = 5,
+ PartialSuccess = 6
+}
+
+///
+/// Extension methods for .
+///
+internal static class DeployStatusExtensions
+{
+ ///
+ /// Whether this status represents a successful status.
+ ///
+ internal static bool IsSuccessfulStatus(this DeploymentStatus status)
+ {
+ return status == DeploymentStatus.Success
+ || status == DeploymentStatus.PartialSuccess;
+ }
+
+ ///
+ /// Whether this status represents a failed status.
+ ///
+ internal static bool IsFailedStatus(this DeploymentStatus status)
+ {
+ return status == DeploymentStatus.Failed
+ || status == DeploymentStatus.Conflict
+ || status == DeploymentStatus.Cancelled
+ || status == DeploymentStatus.Unknown;
+ }
+
+ ///
+ /// Whether this status represents the 'final' status for an on-going deployment operation.
+ ///
+ /// true if is is terminating; false, otherwise
+ internal static bool IsTerminatingStatus(this DeploymentStatus status)
+ {
+ return status.IsSuccessfulStatus()
+ || status.IsFailedStatus();
+ }
+}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/IDeploymentStatusService.cs b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/IDeploymentStatusService.cs
new file mode 100644
index 000000000000..94242b6574c1
--- /dev/null
+++ b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/IDeploymentStatusService.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.NET.Sdk.Publish.Tasks.OneDeploy;
+
+///
+/// A service that monitors a deployment operation.
+///
+internal interface IDeploymentStatusService
+{
+ ///
+ /// Polls a deployment operation status.
+ ///
+ /// deployment response type
+ /// HTTP client
+ /// URL endpoint to poll the deployment
+ /// user name
+ /// user password
+ /// 'UserAgent' header value
+ /// cancellation token
+ /// the resulting deployment response
+ Task PollDeploymentAsync(IHttpClient httpClient, string url, string user, string password, string userAgent, CancellationToken cancellation);
+}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/IFilePackager.cs b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/IFilePackager.cs
new file mode 100644
index 000000000000..e66e3bba8033
--- /dev/null
+++ b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/IFilePackager.cs
@@ -0,0 +1,24 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.NET.Sdk.Publish.Tasks.OneDeploy;
+
+///
+/// Takes the content off of a location and produces a package (e.g., a .zip file).
+///
+internal interface IFilePackager
+{
+ ///
+ /// Extension of produced package (including the '.'). E.g.: '.zip', '.tar', etc.
+ ///
+ string Extension { get; }
+
+ ///
+ /// Packages the content in the given sourcePath
+ ///
+ /// path to the content to package
+ /// path to the packaged content
+ /// cancellation token
+ /// whether content was packaged or not
+ Task CreatePackageAsync(string sourcePath, string destinationPath, CancellationToken cancellation);
+}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/ITaskLogger.cs b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/ITaskLogger.cs
new file mode 100644
index 000000000000..051f82d001c2
--- /dev/null
+++ b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/ITaskLogger.cs
@@ -0,0 +1,42 @@
+// 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;
+
+namespace Microsoft.NET.Sdk.Publish.Tasks.OneDeploy;
+
+///
+/// A simple logger definition for a .
+///
+internal interface ITaskLogger
+{
+ ///
+ /// Whether this logger is enabled.
+ ///
+ bool Enabled { get; set; }
+
+ ///
+ /// Logs a message.
+ ///
+ /// message to log
+ void LogMessage(string message);
+
+ ///
+ /// Logs a message.
+ ///
+ /// message importance
+ /// message to log
+ void LogMessage(MessageImportance importance, string message);
+
+ ///
+ /// Logs an error message.
+ ///
+ /// message to log
+ void LogError(string message);
+
+ ///
+ /// Logs a warning message.
+ ///
+ /// message to log
+ void LogWarning(string message);
+}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/OneDeploy.WebJob.cs b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/OneDeploy.WebJob.cs
new file mode 100644
index 000000000000..92eb1fc55149
--- /dev/null
+++ b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/OneDeploy.WebJob.cs
@@ -0,0 +1,62 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.NET.Sdk.Publish.Tasks.OneDeploy;
+
+///
+/// 'OneDeploy' publish task for WebJobs.
+///
+public partial class OneDeploy
+{
+ private const string ContinuousWebJobType = "Continuous";
+ private const string TriggeredWebJobType = "Triggered";
+ private const string ContinuousWebJobApiPath = "continuouswebjobs";
+ private const string TriggeredWebJobsApiPath = "triggeredwebjobs";
+
+ public string WebJobName { get; set; }
+
+ public string WebJobType { get; set; }
+
+ ///
+ /// Whether the name is a non-empty value and type is either 'Continuous' or 'Triggered'.
+ ///
+ private bool IsWebJobProject(string webjobName, string webjobType) =>
+ !string.IsNullOrEmpty(webjobName) &&
+ (string.Equals(ContinuousWebJobType, webjobType, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(TriggeredWebJobType, webjobType, StringComparison.OrdinalIgnoreCase));
+
+ private async Task DeployWebJobAsync(
+ IHttpClient httpClient,
+ Uri publishUri,
+ string username,
+ string password,
+ string userAgent,
+ string fileToPublish,
+ FileStream fileToPublishStream,
+ CancellationToken cancellationToken)
+ {
+ var response = await httpClient.PutRequestAsync(
+ publishUri, username, password, DefaultRequestContentType, userAgent, Path.GetFileName(fileToPublish), Encoding.UTF8, fileToPublishStream, cancellationToken);
+
+ return response;
+ }
+
+ private bool GetWebJobPublishUri(string publishUrl, string webjobName, string webjobType, out Uri publishUri)
+ {
+ // action path differs by WebJob type
+ var path = string.Equals(ContinuousWebJobType, webjobType, StringComparison.OrdinalIgnoreCase)
+ ? ContinuousWebJobApiPath
+ : TriggeredWebJobsApiPath;
+
+ // Either:
+ // {publishUrl}/api/continuouswebjobs/{WebJobName}
+ // {publishUrl}/api/triggeredwebjobs/{WebJobName}
+ publishUri = new UriBuilder(publishUrl)
+ {
+ Path = $"api/{path}/{webjobName}",
+ Port = -1
+ }.Uri;
+
+ return Uri.TryCreate(publishUri.ToString(), UriKind.Absolute, out _);
+ }
+}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/OneDeploy.cs b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/OneDeploy.cs
new file mode 100644
index 000000000000..102ada7e76de
--- /dev/null
+++ b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/OneDeploy.cs
@@ -0,0 +1,227 @@
+// 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;
+
+///
+/// 'OneDeploy' publish task.
+///
+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; }
+
+ ///
+ public override bool Execute()
+ {
+ var deployTask = OneDeployAsync(FileToPublishPath, Username, Password, PublishUrl, UserAgentVersion, WebJobName, WebJobType);
+ deployTask.Wait();
+
+ return deployTask.Result;
+ }
+
+ public async Task 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 OneDeployAsync(
+ string fileToPublishPath,
+ string username,
+ string password,
+ string url,
+ string userAgent,
+ string webjobName,
+ string webjobType,
+ IHttpClient httpClient,
+ IDeploymentStatusService 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))
+ {
+ _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())
+ {
+ var responseText = 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())
+ {
+ _taskLogger.LogMessage(MessageImportance.High, Resources.ONEDEPLOY_Success);
+ return true;
+ }
+
+ if (deploymentResponse.IsFailedResponse())
+ {
+ _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);
+
+ return hostObj.ExtractCredentials(out user, out password);
+ }
+
+ private Task 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 (string.IsNullOrEmpty(publishUrl) ||
+ !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 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 _);
+ }
+}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/OneDeployStatusService.cs b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/OneDeployStatusService.cs
new file mode 100644
index 000000000000..2929437335b5
--- /dev/null
+++ b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/OneDeployStatusService.cs
@@ -0,0 +1,83 @@
+// 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.Http;
+using Microsoft.NET.Sdk.Publish.Tasks.Properties;
+
+namespace Microsoft.NET.Sdk.Publish.Tasks.OneDeploy;
+
+internal class OneDeployStatusService(ITaskLogger taskLogger = null) : IDeploymentStatusService
+{
+ private static readonly TimeSpan s_maximumWaitForResult = TimeSpan.FromMinutes(3);
+ private static readonly TimeSpan s_refreshDelay = TimeSpan.FromSeconds(3);
+ private static readonly TimeSpan s_retryDelay = TimeSpan.FromSeconds(1);
+ private static readonly int s_retryCount = 3;
+
+ private readonly ITaskLogger _taskLogger = taskLogger;
+
+ ///
+ public async Task PollDeploymentAsync(
+ IHttpClient httpClient,
+ string url,
+ string user,
+ string password,
+ string userAgent,
+ CancellationToken cancellationToken)
+ {
+ _taskLogger?.LogMessage(Resources.DeploymentStatus_Polling);
+
+ if (httpClient is null || cancellationToken.IsCancellationRequested)
+ {
+ return DeploymentResponse.s_unknownResponse;
+ }
+
+ if (!Uri.TryCreate(url, UriKind.Absolute, out var _))
+ {
+ _taskLogger?.LogError(string.Format(Resources.DeploymentStatus_InvalidPollingUrl, url));
+ return DeploymentResponse.s_unknownResponse;
+ }
+
+ var maxWaitForResultTokenSource = new CancellationTokenSource(s_maximumWaitForResult);
+ var retryTokenSource = CancellationTokenSource.CreateLinkedTokenSource(maxWaitForResultTokenSource.Token, cancellationToken);
+ var retryToken = retryTokenSource.Token;
+
+ DeploymentResponse deploymentResponse = null;
+ DeploymentStatus deployStatus = DeploymentStatus.Pending;
+
+ try
+ {
+ retryTokenSource.CancelAfter(s_maximumWaitForResult);
+
+ while (!retryToken.IsCancellationRequested && !deployStatus.IsTerminatingStatus())
+ {
+ deploymentResponse = await httpClient.RetryGetRequestAsync(
+ url, user, password, userAgent, s_retryCount, s_retryDelay, retryToken);
+
+ // set to 'Unknown' if no response is returned
+ deploymentResponse ??= DeploymentResponse.s_unknownResponse;
+
+ deployStatus = deploymentResponse is not null && deploymentResponse.Status is not null
+ ? deploymentResponse.Status.Value
+ : DeploymentStatus.Unknown;
+
+ _taskLogger?.LogMessage(deploymentResponse.ToString());
+
+ await System.Threading.Tasks.Task.Delay(s_refreshDelay, retryToken);
+ }
+ }
+ catch (Exception ex)
+ {
+ if (ex is TaskCanceledException)
+ {
+ // 'maxWaitTimeForResult' has passed; no 'terminating' status was obtained
+ }
+ else if (ex is HttpRequestException)
+ {
+ // HTTP GET request threw; return last known response
+ return deploymentResponse;
+ }
+ }
+
+ return deploymentResponse ?? DeploymentResponse.s_unknownResponse;
+ }
+}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/TaskLogger.cs b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/TaskLogger.cs
new file mode 100644
index 000000000000..72d4dbe12650
--- /dev/null
+++ b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/TaskLogger.cs
@@ -0,0 +1,58 @@
+// 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.Build.Utilities;
+
+namespace Microsoft.NET.Sdk.Publish.Tasks.OneDeploy;
+
+///
+/// A logger that wraps a instance.
+///
+internal class TaskLogger(TaskLoggingHelper logger, bool isEnabled = true) : ITaskLogger
+{
+ private readonly TaskLoggingHelper _taskLoggingHelper = logger;
+
+ ///
+ public bool Enabled { get; set; } = isEnabled;
+
+ private bool CanLog => _taskLoggingHelper is not null && Enabled;
+
+ ///
+ public void LogMessage(string message)
+ {
+ if (CanLog)
+ {
+ _taskLoggingHelper.LogMessage(message);
+ }
+ }
+
+ ///
+ public void LogMessage(MessageImportance importance, string message)
+ {
+ if (CanLog)
+ {
+ _taskLoggingHelper.LogMessage(importance, message);
+
+ }
+ }
+
+ ///
+ public void LogError(string message)
+ {
+ if (CanLog)
+ {
+ _taskLoggingHelper.LogError(message);
+
+ }
+ }
+
+ ///
+ public void LogWarning(string message)
+ {
+ if (CanLog)
+ {
+ _taskLoggingHelper.LogWarning(message);
+ }
+ }
+}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/ZipFilePackager.cs b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/ZipFilePackager.cs
new file mode 100644
index 000000000000..7457d91b3f3b
--- /dev/null
+++ b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/ZipFilePackager.cs
@@ -0,0 +1,24 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.IO.Compression;
+
+namespace Microsoft.NET.Sdk.Publish.Tasks.OneDeploy;
+
+///
+/// Packages file(s) to a .zip file.
+///
+internal class ZipFilePackager : IFilePackager
+{
+ ///
+ public string Extension => ".zip";
+
+ ///
+ public Task CreatePackageAsync(string sourcePath, string destinationPath, CancellationToken cancellation)
+ {
+ ZipFile.CreateFromDirectory(sourcePath, destinationPath);
+
+ // no way to know if ZipFile succeeded; assume it always does
+ return System.Threading.Tasks.Task.FromResult(true);
+ }
+}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/DefaultHttpClient.cs b/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/DefaultHttpClient.cs
deleted file mode 100644
index 5ee89130410c..000000000000
--- a/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/DefaultHttpClient.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-// 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.Http;
-using System.Net.Http.Headers;
-
-namespace Microsoft.NET.Sdk.Publish.Tasks.ZipDeploy
-{
- internal class DefaultHttpClient : IHttpClient, IDisposable
- {
- private readonly HttpClient _httpClient = new()
- {
- Timeout = Timeout.InfiniteTimeSpan
- };
-
- public HttpRequestHeaders DefaultRequestHeaders => _httpClient.DefaultRequestHeaders;
-
- public void Dispose()
- {
- _httpClient.Dispose();
- }
-
- public Task PostAsync(Uri uri, StreamContent content)
- {
- return _httpClient.PostAsync(uri, content);
- }
-
- public Task GetAsync(Uri uri, CancellationToken cancellationToken)
- {
- return _httpClient.GetAsync(uri, cancellationToken);
- }
- }
-}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/HttpClientHelpers.cs b/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/HttpClientHelpers.cs
deleted file mode 100644
index 9a877b2e86c3..000000000000
--- a/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/HttpClientHelpers.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-// 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 System.Net.Http;
-using System.Net.Http.Headers;
-
-namespace Microsoft.NET.Sdk.Publish.Tasks.ZipDeploy
-{
- internal static class HttpClientHelpers
- {
- internal static readonly string AzureADUserName = Guid.Empty.ToString();
- internal static readonly string BearerAuthenticationScheme = "Bearer";
- internal static readonly string BasicAuthenticationScheme = "Basic";
- public static async Task PostRequestAsync(this IHttpClient client, Uri uri, string username, string password, string contentType, string userAgent, Encoding encoding, Stream messageBody)
- {
- AddAuthenticationHeader(username, password, client);
- client.DefaultRequestHeaders.Add("User-Agent", userAgent);
-
- StreamContent content = new(messageBody ?? new MemoryStream())
- {
- Headers =
- {
- ContentType = new MediaTypeHeaderValue(contentType)
- {
- CharSet = encoding.WebName
- },
- ContentEncoding =
- {
- encoding.WebName
- }
- }
- };
-
- try
- {
- HttpResponseMessage responseMessage = await client.PostAsync(uri, content);
- return new HttpResponseMessageWrapper(responseMessage);
- }
- catch (TaskCanceledException)
- {
- return new HttpResponseMessageForStatusCode(HttpStatusCode.RequestTimeout);
- }
- }
-
- public static async Task GetRequestAsync(this IHttpClient client, Uri uri, string username, string password, string userAgent, CancellationToken cancellationToken)
- {
- AddAuthenticationHeader(username, password, client);
- client.DefaultRequestHeaders.Add("User-Agent", userAgent);
-
- try
- {
- HttpResponseMessage responseMessage = await client.GetAsync(uri, cancellationToken);
- return new HttpResponseMessageWrapper(responseMessage);
- }
- catch (TaskCanceledException)
- {
- return new HttpResponseMessageForStatusCode(HttpStatusCode.RequestTimeout);
- }
- }
-
- private static void AddAuthenticationHeader(string username, string password, IHttpClient client)
- {
- client.DefaultRequestHeaders.Remove("Connection");
-
- if (!string.Equals(username, AzureADUserName, StringComparison.Ordinal))
- {
- string plainAuth = string.Format("{0}:{1}", username, password);
- byte[] plainAuthBytes = Encoding.ASCII.GetBytes(plainAuth);
- string base64 = Convert.ToBase64String(plainAuthBytes);
- client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(BasicAuthenticationScheme, base64);
- }
- else
- {
- client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(BearerAuthenticationScheme, password);
- }
- }
- }
-}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/HttpResponseMessageForStatusCode.cs b/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/HttpResponseMessageForStatusCode.cs
deleted file mode 100644
index 30604c28fa38..000000000000
--- a/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/HttpResponseMessageForStatusCode.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-// 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;
-
-namespace Microsoft.NET.Sdk.Publish.Tasks.ZipDeploy
-{
- internal class HttpResponseMessageForStatusCode : IHttpResponse
- {
- public HttpResponseMessageForStatusCode(HttpStatusCode statusCode)
- {
- StatusCode = statusCode;
- }
-
- public HttpStatusCode StatusCode { get; private set; }
-
- public System.Threading.Tasks.Task GetResponseBodyAsync()
- {
- return System.Threading.Tasks.Task.FromResult(new MemoryStream());
- }
-
- public IEnumerable GetHeader(string name)
- {
- return new string[0];
- }
- }
-}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/HttpResponseMessageWrapper.cs b/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/HttpResponseMessageWrapper.cs
deleted file mode 100644
index 2ad5d693d2e7..000000000000
--- a/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/HttpResponseMessageWrapper.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-// 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 System.Net.Http;
-
-namespace Microsoft.NET.Sdk.Publish.Tasks.ZipDeploy
-{
- public class HttpResponseMessageWrapper : IHttpResponse
- {
- private readonly HttpResponseMessage _message;
- private readonly Lazy> _responseBodyTask;
-
- public HttpResponseMessageWrapper(HttpResponseMessage message)
- {
- _message = message;
- StatusCode = message.StatusCode;
- _responseBodyTask = new Lazy>(GetResponseStream);
- }
-
- public HttpStatusCode StatusCode { get; private set; }
-
- public async Task GetResponseBodyAsync()
- {
- return await _responseBodyTask.Value;
- }
-
- private Task GetResponseStream()
- {
- return _message.Content.ReadAsStreamAsync();
- }
-
- public IEnumerable GetHeader(string name)
- {
- IEnumerable values;
- if (_message.Headers.TryGetValues(name, out values))
- {
- return values;
- }
-
- return new string[0];
- }
- }
-}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/IHttpClient.cs b/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/IHttpClient.cs
deleted file mode 100644
index 58af372a48a0..000000000000
--- a/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/IHttpClient.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-// 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.Http;
-using System.Net.Http.Headers;
-
-namespace Microsoft.NET.Sdk.Publish.Tasks.ZipDeploy
-{
- public interface IHttpClient
- {
- HttpRequestHeaders DefaultRequestHeaders { get; }
- Task PostAsync(Uri uri, StreamContent content);
- Task GetAsync(Uri uri, CancellationToken cancellationToken);
- }
-}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/IHttpResponse.cs b/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/IHttpResponse.cs
deleted file mode 100644
index 914832aad511..000000000000
--- a/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/IHttpResponse.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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;
-
-namespace Microsoft.NET.Sdk.Publish.Tasks.ZipDeploy
-{
- ///
- /// A response to an HTTP request
- ///
- public interface IHttpResponse
- {
- ///
- /// Gets the status code the server returned
- ///
- HttpStatusCode StatusCode { get; }
-
- ///
- /// Gets the body of the response
- ///
- Task GetResponseBodyAsync();
-
- IEnumerable GetHeader(string name);
- }
-}
diff --git a/src/WebSdk/README.md b/src/WebSdk/README.md
index 488919c43791..5b32697f7a50 100644
--- a/src/WebSdk/README.md
+++ b/src/WebSdk/README.md
@@ -144,6 +144,24 @@ Using dotnet with a profile:
dotnet publish WebApplication.csproj /p:PublishProfile= /p:Password=
```
+One Deploy:
+---------------------
+
+Using dotnet with the default profile:
+
+```
+
+dotnet publish WebJobApplication.csproj /p:WebPublishMethod=OneDeploy /p:PublishUrl= /p:UserName= /p:Password= /p:PublishProfile=DefaultWebJobOneDeploy
+```
+
+Profile can be added to the following location in the project /Properties/PublishProfiles/.
+
+Using dotnet with a profile:
+
+```
+dotnet publish WebJobApplication.csproj /p:PublishProfile= /p:Password=
+```
+
Sample folder profile:
---------------------
diff --git a/test/Microsoft.NET.Sdk.Publish.Tasks.Tests/Tasks/OneDeploy/CreatePackageFileTests.cs b/test/Microsoft.NET.Sdk.Publish.Tasks.Tests/Tasks/OneDeploy/CreatePackageFileTests.cs
new file mode 100644
index 000000000000..c0b0c8bac40d
--- /dev/null
+++ b/test/Microsoft.NET.Sdk.Publish.Tasks.Tests/Tasks/OneDeploy/CreatePackageFileTests.cs
@@ -0,0 +1,99 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Moq;
+
+namespace Microsoft.NET.Sdk.Publish.Tasks.OneDeploy.Tests;
+
+///
+/// Unit Tests for .
+///
+public class CreatePackageFileTests
+{
+ private const string TestPackageExtension = ".test";
+ private const string ProjectName = "TestProject";
+ private const string ContentToPackage = $@"z:\Users\testUser\source\Solution\{ProjectName}";
+ private const string IntermediateTempPath = $@"{ContentToPackage}\bin\net8.0\{ProjectName}";
+
+ [Theory]
+ [InlineData(true, TestPackageExtension, IntermediateTempPath)]
+ [InlineData(false, null, null)]
+ public void CreatePackageFile_Execute(bool expectedResult, string expectedFileExtension, string expectedFileDirectory)
+ {
+ // Arrange
+ var testPackageFilePath = Path.Combine(IntermediateTempPath, "uniqueFileName");
+
+ var filePackagerMock = new Mock();
+ filePackagerMock.SetupGet(fp => fp.Extension).Returns(TestPackageExtension);
+ filePackagerMock
+ .Setup(fp => fp.CreatePackageAsync(ContentToPackage, It.IsAny(), It.IsAny()))
+ .ReturnsAsync(expectedResult);
+
+ var createPackageFileTask = new CreatePackageFile(filePackagerMock.Object)
+ {
+ ContentToPackage = ContentToPackage,
+ ProjectName = ProjectName,
+ IntermediateTempPath = IntermediateTempPath,
+ };
+
+ // Act
+ var result = createPackageFileTask.Execute();
+
+ // Assert: 'CreatePackageFile' task result expected results
+ Assert.Equal(expectedResult, result);
+
+ if (expectedResult)
+ {
+ Assert.Equal(expectedFileDirectory, Path.GetDirectoryName(createPackageFileTask.CreatedPackageFilePath));
+ Assert.Equal(expectedFileExtension, Path.GetExtension(createPackageFileTask.CreatedPackageFilePath));
+ }
+ else
+ {
+ Assert.True(string.IsNullOrEmpty(createPackageFileTask.CreatedPackageFilePath));
+ }
+
+ filePackagerMock.VerifyAll();
+ }
+
+ [Theory]
+ [InlineData(null, ProjectName, IntermediateTempPath)]
+ [InlineData("", ProjectName, IntermediateTempPath)]
+ [InlineData(ContentToPackage, null, IntermediateTempPath)]
+ [InlineData(ContentToPackage, "", IntermediateTempPath)]
+ [InlineData(ContentToPackage, ProjectName, null)]
+ [InlineData(ContentToPackage, ProjectName, "")]
+ [InlineData("", "", "")]
+ [InlineData(null, null, null)]
+ public void CreatePackageFile_Execute_MissingValues(string contentToPackage, string projectName, string intermediateTempPath)
+ {
+ // Arrange
+ var filePackagerMock = new Mock();
+
+ var createPackageFileTask = new CreatePackageFile(filePackagerMock.Object)
+ {
+ ContentToPackage = contentToPackage,
+ ProjectName = projectName,
+ IntermediateTempPath = intermediateTempPath,
+ };
+
+ // Act
+ var result = createPackageFileTask.Execute();
+
+ // Assert: 'CreatePackageFile' task results in 'False' due to missing values
+ Assert.False(result);
+ Assert.True(string.IsNullOrEmpty(createPackageFileTask.CreatedPackageFilePath));
+ filePackagerMock.VerifyAll();
+ }
+
+ [Fact]
+ public void CreatePackagerFile_ZipPackager_Default()
+ {
+ // Act
+ var createPackageFile = new CreatePackageFile();
+
+ // Assert:
+ // - Default ctor (as used by MSBuild) can correctly instantiate an instance.
+ // - A 'ZipFilePackager' is set as the default 'IFilePackager' (though we can't verify that, here)
+ Assert.True(createPackageFile is not null);
+ }
+}
diff --git a/test/Microsoft.NET.Sdk.Publish.Tasks.Tests/Tasks/OneDeploy/OneDeployStatusServiceTests.cs b/test/Microsoft.NET.Sdk.Publish.Tasks.Tests/Tasks/OneDeploy/OneDeployStatusServiceTests.cs
new file mode 100644
index 000000000000..52c5c3de3129
--- /dev/null
+++ b/test/Microsoft.NET.Sdk.Publish.Tasks.Tests/Tasks/OneDeploy/OneDeployStatusServiceTests.cs
@@ -0,0 +1,240 @@
+// 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 System.Net.Http;
+using System.Text.Json;
+using Microsoft.NET.Sdk.Publish.Tasks.Properties;
+using Moq;
+
+namespace Microsoft.NET.Sdk.Publish.Tasks.OneDeploy.Tests;
+
+///
+/// Unit Tests for
+///
+public class OneDeployStatusServiceTests
+{
+ private const string Username = "someUser";
+ private const string Password = "123secret";
+ private const string UserAgent = "websdk/8.0"; // as OneDeploy.UserAgentName
+ private const string PublishUrl = "https://mysite.scm.azurewebsites.net";
+ private const string DeploymentId = "056f49ce-fcd7-497c-929b-d74bc6f8905e";
+
+ private static readonly Uri DeploymentUri = new UriBuilder($@"{PublishUrl}/api/deployments/{DeploymentId}").Uri;
+
+ [Theory]
+ [InlineData(HttpStatusCode.OK, DeploymentStatus.Success)]
+ [InlineData(HttpStatusCode.Accepted, DeploymentStatus.Success)]
+ [InlineData(HttpStatusCode.OK, DeploymentStatus.PartialSuccess)]
+ [InlineData(HttpStatusCode.Accepted, DeploymentStatus.PartialSuccess)]
+ [InlineData(HttpStatusCode.OK, DeploymentStatus.Failed)]
+ [InlineData(HttpStatusCode.Accepted, DeploymentStatus.Failed)]
+ [InlineData(HttpStatusCode.OK, DeploymentStatus.Conflict)]
+ [InlineData(HttpStatusCode.Accepted, DeploymentStatus.Conflict)]
+ [InlineData(HttpStatusCode.OK, DeploymentStatus.Cancelled)]
+ [InlineData(HttpStatusCode.Accepted, DeploymentStatus.Cancelled)]
+ [InlineData(HttpStatusCode.OK, DeploymentStatus.Unknown)]
+ [InlineData(HttpStatusCode.Accepted, DeploymentStatus.Unknown)]
+ public async Task PollDeployment_Completes(HttpStatusCode statusCode, DeploymentStatus expectedDeploymentStatus)
+ {
+ // Arrange
+ var cancellationTokenSource = new CancellationTokenSource();
+ var cancellationToken = cancellationTokenSource.Token;
+
+ var taskLoggerMock = new Mock();
+ taskLoggerMock.Setup(l => l.LogMessage(Resources.DeploymentStatus_Polling));
+ taskLoggerMock.Setup(l => l.LogMessage(string.Format(Resources.DeploymentStatus, expectedDeploymentStatus)));
+
+ var deploymentResponse = new DeploymentResponse();
+ if (expectedDeploymentStatus != DeploymentStatus.Unknown)
+ {
+ deploymentResponse.Id = DeploymentId;
+ deploymentResponse.Status = expectedDeploymentStatus;
+ }
+
+ var httpClientMock = GetHttpClientMock(statusCode, deploymentResponse);
+
+ var oneDeployStatusService = new OneDeployStatusService(taskLoggerMock.Object);
+
+ // Act
+ var result = await oneDeployStatusService.PollDeploymentAsync(httpClientMock.Object, DeploymentUri.AbsoluteUri, Username, Password, UserAgent, cancellationToken);
+
+ // Assert: poll deployment status runs to completion, resulting in the given expectedDeploymentStatus
+ Assert.Equal(expectedDeploymentStatus, deploymentResponse.Status);
+
+ taskLoggerMock.VerifyAll();
+ httpClientMock.VerifyAll();
+ }
+
+ [Theory]
+ [InlineData(HttpStatusCode.Forbidden)]
+ [InlineData(HttpStatusCode.NotFound)]
+ [InlineData(HttpStatusCode.InternalServerError)]
+ public async Task PollDeployment_HttpResponse_Fail(HttpStatusCode statusCode)
+ {
+ // Arrange
+ var cancellationTokenSource = new CancellationTokenSource();
+ var cancellationToken = cancellationTokenSource.Token;
+
+ var taskLoggerMock = new Mock();
+ taskLoggerMock.Setup(l => l.LogMessage(Resources.DeploymentStatus_Polling));
+ taskLoggerMock.Setup(l => l.LogMessage(string.Format(Resources.DeploymentStatus, DeploymentStatus.Unknown)));
+
+ var httpClientMock = GetHttpClientMock(statusCode, null);
+
+ var oneDeployStatusService = new OneDeployStatusService(taskLoggerMock.Object);
+
+ // Act
+ var result = await oneDeployStatusService.PollDeploymentAsync(httpClientMock.Object, DeploymentUri.AbsoluteUri, Username, Password, UserAgent, cancellationToken);
+
+ // Assert: poll deployment status runs to completion, resulting in 'Unknown' status because failed HTTP Response Status code
+ Assert.Equal(DeploymentStatus.Unknown, result.Status);
+
+ taskLoggerMock.VerifyAll();
+ httpClientMock.VerifyAll();
+ }
+
+ [Fact]
+ public async Task PollDeployment_Completes_No_Logger()
+ {
+ // Arrange
+ var cancellationTokenSource = new CancellationTokenSource();
+ var cancellationToken = cancellationTokenSource.Token;
+
+ var deploymentResponse = new DeploymentResponse()
+ {
+ Id = DeploymentId,
+ Status = DeploymentStatus.Success
+ };
+
+ var httpClientMock = GetHttpClientMock(HttpStatusCode.OK, deploymentResponse);
+
+ var oneDeployStatusService = new OneDeployStatusService();
+
+ // Act
+ var result = await oneDeployStatusService.PollDeploymentAsync(httpClientMock.Object, DeploymentUri.AbsoluteUri, Username, Password, UserAgent, cancellationToken);
+
+ // Assert: poll deployment status runs to completion with NULL ITaskLogger
+ Assert.Equal(DeploymentStatus.Success, deploymentResponse.Status);
+
+ httpClientMock.VerifyAll();
+ }
+
+ [Fact]
+ public async Task PollDeployment_Halted()
+ {
+ // Arrange
+ var cancellationTokenSource = new CancellationTokenSource();
+ var cancellationToken = cancellationTokenSource.Token;
+
+ var taskLoggerMock = new Mock();
+ taskLoggerMock.Setup(l => l.LogMessage(Resources.DeploymentStatus_Polling));
+
+ var httpClientMock = new Mock();
+
+ var oneDeployStatusService = new OneDeployStatusService(taskLoggerMock.Object);
+
+ // Act
+ cancellationTokenSource.Cancel();
+ var result = await oneDeployStatusService.PollDeploymentAsync(httpClientMock.Object, DeploymentUri.AbsoluteUri, Username, Password, UserAgent, cancellationToken);
+
+ // Assert: deployment status won't poll for deployment as 'CancellationToken' is already cancelled
+ Assert.Equal(DeploymentStatus.Unknown, result.Status);
+
+ taskLoggerMock.VerifyAll();
+ httpClientMock.VerifyAll();
+ }
+
+ [Theory]
+ [InlineData("not-valid-url")]
+ [InlineData("")]
+ [InlineData(null)]
+ public async Task PollDeployment_InvalidURL(string invalidUrl)
+ {
+ // Arrange
+ var cancellationTokenSource = new CancellationTokenSource();
+ var cancellationToken = cancellationTokenSource.Token;
+
+ var taskLoggerMock = new Mock();
+ taskLoggerMock.Setup(l => l.LogMessage(Resources.DeploymentStatus_Polling));
+ taskLoggerMock.Setup(l => l.LogError(string.Format(Resources.DeploymentStatus_InvalidPollingUrl, invalidUrl)));
+
+ var httpClientMock = new Mock();
+
+ var oneDeployStatusService = new OneDeployStatusService(taskLoggerMock.Object);
+
+ // Act
+ var result = await oneDeployStatusService.PollDeploymentAsync(httpClientMock.Object, invalidUrl, Username, Password, UserAgent, cancellationToken);
+
+ // Assert: deployment status won't poll for deployment because given polling URL is invalid
+ Assert.Equal(DeploymentStatus.Unknown, result.Status);
+
+ taskLoggerMock.VerifyAll();
+ httpClientMock.VerifyAll();
+ }
+
+ [Fact]
+ public async Task PollDeployment_Missing_HttpClient()
+ {
+ // Arrange
+ var cancellationTokenSource = new CancellationTokenSource();
+ var cancellationToken = cancellationTokenSource.Token;
+
+ var taskLoggerMock = new Mock();
+ taskLoggerMock.Setup(l => l.LogMessage(Resources.DeploymentStatus_Polling));
+
+ var oneDeployStatusService = new OneDeployStatusService(taskLoggerMock.Object);
+
+ // Act
+ var result = await oneDeployStatusService.PollDeploymentAsync(null, DeploymentUri.AbsoluteUri, Username, Password, UserAgent, cancellationToken);
+
+ // Assert: deployment status won't poll for deployment because IHttpClient is NULL
+ Assert.Equal(DeploymentStatus.Unknown, result.Status);
+
+ taskLoggerMock.VerifyAll();
+ }
+
+ [Fact]
+ public void Constructor_OK()
+ {
+ var oneDeployStatusService = new OneDeployStatusService();
+
+ // no-arg ctor instantiate instance
+ Assert.NotNull(oneDeployStatusService);
+ }
+
+ private Mock GetHttpClientMock(
+ HttpStatusCode statusCode,
+ DeploymentResponse deploymentResponse)
+ {
+ var httpClientMock = new Mock();
+
+ // Request
+ HttpRequestMessage requestMessage = new();
+ httpClientMock
+ .Setup(hc => hc.DefaultRequestHeaders)
+ .Returns(requestMessage.Headers);
+
+ // Response
+ HttpContent responseContent = null;
+ string deploymentResponseJson = null;
+ if (deploymentResponse is not null)
+ {
+ deploymentResponseJson = JsonSerializer.Serialize(deploymentResponse);
+ responseContent = new StringContent(deploymentResponseJson, Encoding.UTF8, "application/json");
+ }
+
+ HttpResponseMessage responseMessage = new(statusCode);
+ if (responseContent is not null)
+ {
+ responseMessage.Content = responseContent;
+ }
+
+ // GetAsync()
+ httpClientMock
+ .Setup(hc => hc.GetAsync(DeploymentUri, It.IsAny()))
+ .ReturnsAsync(responseMessage);
+
+ return httpClientMock;
+ }
+}
diff --git a/test/Microsoft.NET.Sdk.Publish.Tasks.Tests/Tasks/OneDeploy/OneDeployTests.WebJob.cs b/test/Microsoft.NET.Sdk.Publish.Tasks.Tests/Tasks/OneDeploy/OneDeployTests.WebJob.cs
new file mode 100644
index 000000000000..dd0ea370efb3
--- /dev/null
+++ b/test/Microsoft.NET.Sdk.Publish.Tasks.Tests/Tasks/OneDeploy/OneDeployTests.WebJob.cs
@@ -0,0 +1,193 @@
+// 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 System.Net.Http;
+using System.Security.Policy;
+using Microsoft.NET.Sdk.Publish.Tasks.Properties;
+using Moq;
+
+namespace Microsoft.NET.Sdk.Publish.Tasks.OneDeploy.Tests;
+
+public partial class OneDeployTests
+{
+ private const string WebJobName = "TestWebJob";
+ private const string ContinuousWebJob = "Continuous"; // as OneDeploy.WebJob.ContinuousWebJobType
+ private const string TriggeredWebJob = "Triggered"; // as OneDeploy.WebJob.TriggeredWebJobType
+ private const string ContinuousApiPath = "api/continuouswebjobs"; // as OneDeploy.WebJob.ContinuousWebJobApiPath
+ private const string TriggeredApiPath = "api/triggeredwebjobs"; // as OneDeploy.WebJob.TriggeredWebJobsApiPath
+ private const string PutErrorResponseMessage = "Missing run.sh file";
+
+ private static readonly Uri OneDeploy_WebJob_Continuous_Uri = new UriBuilder(PublishUrl)
+ {
+ Path = $"{ContinuousApiPath}/{WebJobName}",
+ }.Uri;
+
+ private static readonly Uri OneDeploy_WebJob_Triggered_Uri = new UriBuilder(PublishUrl)
+ {
+ Path = $"{TriggeredApiPath}/{WebJobName}",
+ }.Uri;
+
+ [Theory]
+ [InlineData(DeploymentStatus.Success, HttpStatusCode.OK, ContinuousWebJob, true)]
+ [InlineData(DeploymentStatus.Success, HttpStatusCode.OK, TriggeredWebJob, true)]
+ [InlineData(DeploymentStatus.Failed, HttpStatusCode.BadRequest, ContinuousWebJob, false)]
+ [InlineData(DeploymentStatus.Failed, HttpStatusCode.NotFound, TriggeredWebJob, false)]
+ public async Task OneDeploy_WebJob_Execute_Completes(
+ DeploymentStatus deployStatus, HttpStatusCode statusCode, string webJobType, bool expectedResult)
+ {
+ // Arrange
+ var publishUri = GetWebJobUri(webJobType);
+ var httpClientMock = GetWebJobHttpClientMock(publishUri, statusCode, deployStatus);
+
+ var deploymentStatusServiceMock = new Mock>();
+
+ // set messages to log according to result
+ var taskLoggerMock = new Mock();
+ taskLoggerMock.Setup(l => l.LogMessage(Build.Framework.MessageImportance.High, string.Format(Resources.ONEDEPLOY_PublishingOneDeploy, FileToPublish, publishUri.AbsoluteUri.ToString())));
+
+ if (expectedResult)
+ {
+ taskLoggerMock.Setup(l => l.LogMessage(string.Format(Resources.ONEDEPLOY_Uploaded, FileToPublish)));
+ taskLoggerMock.Setup(l => l.LogMessage(Build.Framework.MessageImportance.High, Resources.ONEDEPLOY_Success));
+ }
+ else
+ {
+ var failedDeployMsg = string.Format(Resources.ONEDEPLOY_FailedDeployRequest_With_ResponseText, publishUri.AbsoluteUri, statusCode, PutErrorResponseMessage);
+ taskLoggerMock.Setup(l => l.LogError(failedDeployMsg));
+ }
+
+ var oneDeployTask = new OneDeploy(taskLoggerMock.Object);
+
+ // Act
+ var result = await oneDeployTask.OneDeployAsync(
+ FileToPublish, Username, Password, PublishUrl, $"{UserAgentName}/8.0", WebJobName, webJobType, httpClientMock.Object, deploymentStatusServiceMock.Object, CancellationToken.None);
+
+ // Assert: WebJob deployment operation runs to completion with expected result
+ Assert.Equal(expectedResult, result);
+
+ httpClientMock.VerifyAll();
+ deploymentStatusServiceMock.VerifyAll();
+ taskLoggerMock.VerifyAll();
+ }
+
+ [Theory]
+ [InlineData("not-valid-url", ContinuousWebJob)]
+ [InlineData("not-valid-url", TriggeredWebJob)]
+ [InlineData("", ContinuousWebJob)]
+ [InlineData("", TriggeredWebJob)]
+ [InlineData(null, ContinuousWebJob)]
+ [InlineData(null, TriggeredWebJob)]
+ public async Task OneDeploy_WebJob_PublishUrl_Invalid(string invalidUrl, string webjobType)
+ {
+ // Arrange
+ var httpClientMock = new Mock();
+
+ var deploymentStatusServiceMock = new Mock>();
+
+ // set messages to log
+ var taskLoggerMock = new Mock();
+ taskLoggerMock.Setup(l => l.LogError(string.Format(Resources.ONEDEPLOY_InvalidPublishUrl, invalidUrl)));
+
+ var oneDeployTask = new OneDeploy(taskLoggerMock.Object);
+
+ // Act
+ var result = await oneDeployTask.OneDeployAsync(
+ FileToPublish, Username, Password, invalidUrl, $"{UserAgentName}/8.0", WebJobName, webjobType, httpClientMock.Object, deploymentStatusServiceMock.Object, CancellationToken.None);
+
+ // Assert: deployment operation fails because 'PublishUrl' is not valid
+ Assert.False(result);
+
+ httpClientMock.VerifyAll();
+ deploymentStatusServiceMock.VerifyAll();
+ taskLoggerMock.VerifyAll();
+ }
+
+ [Theory]
+ [InlineData("", ContinuousWebJob)]
+ [InlineData("", TriggeredWebJob)]
+ [InlineData(null, ContinuousWebJob)]
+ [InlineData(null, TriggeredWebJob)]
+ [InlineData(WebJobName, "NotValidType")]
+ [InlineData(WebJobName, "")]
+ [InlineData(WebJobName, null)]
+ [InlineData("", "")]
+ [InlineData(null, null)]
+ public async Task OneDeploy_WebJob_Missing_NameOrType(string webjobName, string webjobType)
+ {
+ // Arrange
+ var httpClientMock = new Mock();
+
+ // Request
+ HttpRequestMessage requestMessage = new();
+ httpClientMock.Setup(hc => hc.DefaultRequestHeaders).Returns(requestMessage.Headers);
+
+ // PostAsync()
+ httpClientMock
+ .Setup(hc => hc.PostAsync(OneDeployUri, It.IsAny()))
+ .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.BadGateway)
+ {
+ Content = new StringContent(PutErrorResponseMessage)
+ }
+ );
+
+ var deploymentStatusServiceMock = new Mock>();
+
+ // set messages to log
+ var taskLoggerMock = new Mock();
+ taskLoggerMock.Setup(l => l.LogMessage(Build.Framework.MessageImportance.High, string.Format(Resources.ONEDEPLOY_PublishingOneDeploy, FileToPublish, OneDeployUri.AbsoluteUri.ToString())));
+ taskLoggerMock.Setup(l => l.LogError(string.Format(Resources.ONEDEPLOY_FailedDeployRequest_With_ResponseText, OneDeployUri.AbsoluteUri, HttpStatusCode.BadGateway, PutErrorResponseMessage)));
+
+ var oneDeployTask = new OneDeploy(taskLoggerMock.Object);
+
+ // Act
+ var result = await oneDeployTask.OneDeployAsync(
+ FileToPublish, Username, Password, PublishUrl, $"{UserAgentName}/8.0", webjobName, webjobType, httpClientMock.Object, deploymentStatusServiceMock.Object, CancellationToken.None);
+
+ // Assert: deployment operation fails because since 'WebJobName' and/or 'WebJobType' is invalid, so we calculate the
+ // default OneDeploy URI ('/api/publish'), which target instance does not recognized as valid
+ Assert.False(result);
+
+ httpClientMock.VerifyAll();
+ deploymentStatusServiceMock.VerifyAll();
+ taskLoggerMock.VerifyAll();
+ }
+
+ private Mock GetWebJobHttpClientMock(
+ Uri publishUri,
+ HttpStatusCode statusCode,
+ DeploymentStatus deploymentStatus)
+ {
+ var httpClientMock = new Mock();
+
+ // Request
+ HttpRequestMessage requestMessage = new();
+ httpClientMock
+ .Setup(hc => hc.DefaultRequestHeaders)
+ .Returns(requestMessage.Headers);
+
+ // Response
+ HttpResponseMessage responseMessage = new(statusCode);
+ if (deploymentStatus.IsFailedStatus())
+ {
+ responseMessage.Content = new StringContent(PutErrorResponseMessage);
+ }
+
+ // PutAsync()
+ httpClientMock
+ .Setup(hc => hc.PutAsync(publishUri, It.IsAny(), It.IsAny()))
+ .ReturnsAsync(responseMessage);
+
+ return httpClientMock;
+ }
+
+ private Uri GetWebJobUri(string webJobType)
+ {
+ return webJobType switch
+ {
+ TriggeredWebJob => OneDeploy_WebJob_Triggered_Uri,
+ ContinuousWebJob
+ or _ => OneDeploy_WebJob_Continuous_Uri
+ };
+ }
+}
diff --git a/test/Microsoft.NET.Sdk.Publish.Tasks.Tests/Tasks/OneDeploy/OneDeployTests.cs b/test/Microsoft.NET.Sdk.Publish.Tasks.Tests/Tasks/OneDeploy/OneDeployTests.cs
new file mode 100644
index 000000000000..936e02882984
--- /dev/null
+++ b/test/Microsoft.NET.Sdk.Publish.Tasks.Tests/Tasks/OneDeploy/OneDeployTests.cs
@@ -0,0 +1,352 @@
+// 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 System.Net.Http;
+using Microsoft.NET.Sdk.Publish.Tasks.MsDeploy;
+using Microsoft.NET.Sdk.Publish.Tasks.Properties;
+using Moq;
+
+namespace Microsoft.NET.Sdk.Publish.Tasks.OneDeploy.Tests;
+
+///
+/// Unit Tests for
+///
+public partial class OneDeployTests
+{
+ private const string Username = "someUser";
+ private const string Password = "123secret";
+ private const string PublishUrl = "https://mysite.scm.azurewebsites.net";
+ private const string UserAgentName = "websdk"; // as OneDeploy.UserAgentName
+ private const string DeploymentUrl = $@"{PublishUrl}/api/deployments/056f49ce-fcd7-497c-929b-d74bc6f8905e";
+ private const string DeploymentLogUrl = $@"{DeploymentUrl}/log";
+ private const string DefaultApiPath = "api/publish"; // as OneDeploy.OneDeployApiPath
+ private const string DefaultQueryParam = "RemoteBuild=false"; // as OneDeploy.OneDeployQueryParam
+
+ private static readonly Uri OneDeployUri = new UriBuilder(PublishUrl)
+ {
+ Path = DefaultApiPath,
+ Query = DefaultQueryParam
+ }.Uri;
+
+ private static string _fileToPublish;
+ public static string FileToPublish
+ {
+ get
+ {
+ if (_fileToPublish == null)
+ {
+ string codebase = typeof(OneDeployTests).Assembly.Location;
+ string assemblyPath = new Uri(codebase, UriKind.Absolute).LocalPath;
+ string baseDirectory = Path.GetDirectoryName(assemblyPath);
+ _fileToPublish = Path.Combine(baseDirectory, Path.Combine("Resources", "TestPublishContents.zip"));
+ }
+
+ return _fileToPublish;
+ }
+ }
+
+ [Theory]
+ [InlineData(DeploymentStatus.Success, HttpStatusCode.OK, true)]
+ [InlineData(DeploymentStatus.Success, HttpStatusCode.Accepted, true)]
+ [InlineData(DeploymentStatus.PartialSuccess, HttpStatusCode.OK, true)]
+ [InlineData(DeploymentStatus.PartialSuccess, HttpStatusCode.Accepted, true)]
+ [InlineData(DeploymentStatus.Failed, HttpStatusCode.OK, false)]
+ [InlineData(DeploymentStatus.Failed, HttpStatusCode.Accepted, false)]
+ [InlineData(DeploymentStatus.Conflict, HttpStatusCode.OK, false)]
+ [InlineData(DeploymentStatus.Conflict, HttpStatusCode.Accepted, false)]
+ [InlineData(DeploymentStatus.Cancelled, HttpStatusCode.OK, false)]
+ [InlineData(DeploymentStatus.Cancelled, HttpStatusCode.Accepted, false)]
+ [InlineData(DeploymentStatus.Unknown, HttpStatusCode.OK, false)]
+ [InlineData(DeploymentStatus.Unknown, HttpStatusCode.Accepted, false)]
+
+ public async Task OneDeploy_Execute_Completes(DeploymentStatus deployStatus, HttpStatusCode statusCode, bool expectedResult)
+ {
+ // Arrange
+ var httpClientMock = GetHttpClientMock(statusCode);
+
+ var deploymentStatusServiceMock = GetDeploymentStatusServiceMock(httpClientMock.Object, deployStatus);
+
+ // set messages to log according to result
+ var taskLoggerMock = new Mock();
+ taskLoggerMock.Setup(l => l.LogMessage(Build.Framework.MessageImportance.High, string.Format(Resources.ONEDEPLOY_PublishingOneDeploy, FileToPublish, OneDeployUri.AbsoluteUri.ToString())));
+ taskLoggerMock.Setup(l => l.LogMessage(string.Format(Resources.ONEDEPLOY_Uploaded, FileToPublish)));
+
+ if (expectedResult)
+ {
+ taskLoggerMock.Setup(l => l.LogMessage(Build.Framework.MessageImportance.High, Resources.ONEDEPLOY_Success));
+ }
+ else
+ {
+ var failedDeployMsg = string.Format(Resources.ONEDEPLOY_FailedWithLogs, FileToPublish, OneDeployUri.AbsoluteUri, deployStatus, DeploymentLogUrl);
+ taskLoggerMock.Setup(l => l.LogError(failedDeployMsg));
+ }
+
+ var oneDeployTask = new OneDeploy(taskLoggerMock.Object);
+
+ // Act
+ var result = await oneDeployTask.OneDeployAsync(
+ FileToPublish, Username, Password, PublishUrl, $"{UserAgentName}/8.0", webjobName: null, webjobType: null, httpClientMock.Object, deploymentStatusServiceMock.Object, CancellationToken.None);
+
+ // Assert: deployment operation runs to completion with expected result
+ Assert.Equal(expectedResult, result);
+
+ httpClientMock.VerifyAll();
+ deploymentStatusServiceMock.VerifyAll();
+ taskLoggerMock.VerifyAll();
+ }
+
+ [Theory]
+ [InlineData("not-a-location-url")]
+ [InlineData(null)]
+ public async Task OneDeploy_Execute_Deploy_Location_Missing(string invalidLocationHeaderValue)
+ {
+ // Arrange
+ var httpClientMock = new Mock();
+
+ // Request
+ HttpRequestMessage requestMessage = new();
+ httpClientMock.Setup(hc => hc.DefaultRequestHeaders).Returns(requestMessage.Headers);
+
+ // Response
+ HttpResponseMessage responseMessage = new(HttpStatusCode.OK);
+ if (invalidLocationHeaderValue is not null)
+ {
+ responseMessage.Headers.Add("Location", invalidLocationHeaderValue);
+ }
+
+ // PostAsync()
+ httpClientMock.Setup(hc => hc.PostAsync(OneDeployUri, It.IsAny())).ReturnsAsync(responseMessage);
+
+ var deploymentStatusServiceMock = new Mock>();
+
+ // set messages to log according to result
+ var taskLoggerMock = new Mock();
+ taskLoggerMock.Setup(l => l.LogMessage(Build.Framework.MessageImportance.High, string.Format(Resources.ONEDEPLOY_PublishingOneDeploy, FileToPublish, OneDeployUri.AbsoluteUri.ToString())));
+ taskLoggerMock.Setup(l => l.LogMessage(string.Format(Resources.ONEDEPLOY_Uploaded, FileToPublish)));
+
+ var oneDeployTask = new OneDeploy(taskLoggerMock.Object);
+
+ // Act
+ var result = await oneDeployTask.OneDeployAsync(
+ FileToPublish, Username, Password, PublishUrl, $"{UserAgentName}/8.0", webjobName: null, webjobType: null, httpClientMock.Object, deploymentStatusServiceMock.Object, CancellationToken.None);
+
+ // Assert: deployment operation runs to completion, without polling the deployment because 'Location' header was not found in response
+ Assert.True(result);
+
+ httpClientMock.VerifyAll();
+ deploymentStatusServiceMock.VerifyAll();
+ taskLoggerMock.VerifyAll();
+ }
+
+ [Theory]
+ [InlineData(HttpStatusCode.Forbidden)]
+ [InlineData(HttpStatusCode.NotFound)]
+ [InlineData(HttpStatusCode.RequestTimeout)]
+ [InlineData(HttpStatusCode.InternalServerError)]
+ public async Task OneDeploy_Execute_HttpResponse_Fail(HttpStatusCode statusCode)
+ {
+ // Arrange
+ var httpClientMock = GetHttpClientMock(statusCode, deployLocationHeader: null);
+
+ var deploymentStatusServiceMock = new Mock>();
+
+ // set messages to log
+ var taskLoggerMock = new Mock();
+ taskLoggerMock.Setup(l => l.LogMessage(Build.Framework.MessageImportance.High, string.Format(Resources.ONEDEPLOY_PublishingOneDeploy, FileToPublish, OneDeployUri.AbsoluteUri.ToString())));
+ taskLoggerMock.Setup(l => l.LogError(string.Format(Resources.ONEDEPLOY_FailedDeployRequest, OneDeployUri.AbsoluteUri.ToString(), statusCode)));
+
+ var oneDeployTask = new OneDeploy(taskLoggerMock.Object);
+
+ // Act
+ var result = await oneDeployTask.OneDeployAsync(
+ FileToPublish, Username, Password, PublishUrl, $"{UserAgentName}/8.0", webjobName: null, webjobType: null, httpClientMock.Object, deploymentStatusServiceMock.Object, CancellationToken.None);
+
+ // Assert: deployment operation fails because HTTP POST request to upload the package returns a failed HTTP Response
+ Assert.False(result);
+
+ httpClientMock.VerifyAll();
+ deploymentStatusServiceMock.VerifyAll();
+ taskLoggerMock.VerifyAll();
+ }
+
+ [Theory]
+ [InlineData("not-valid-url")]
+ [InlineData("")]
+ [InlineData(null)]
+ public async Task OneDeploy_Execute_PublishUrl_Invalid(string invalidUrl)
+ {
+ // Arrange
+ var httpClientMock = new Mock();
+
+ var deploymentStatusServiceMock = new Mock>();
+
+ // set messages to log
+ var taskLoggerMock = new Mock();
+ taskLoggerMock.Setup(l => l.LogError(string.Format(Resources.ONEDEPLOY_InvalidPublishUrl, invalidUrl)));
+
+ var oneDeployTask = new OneDeploy(taskLoggerMock.Object);
+
+ // Act
+ var result = await oneDeployTask.OneDeployAsync(
+ FileToPublish, Username, Password, invalidUrl, $"{UserAgentName}/8.0", webjobName: null, webjobType: null, httpClientMock.Object, deploymentStatusServiceMock.Object, CancellationToken.None);
+
+ // Assert: deployment operation fails because 'PublishUrl' is not valid
+ Assert.False(result);
+
+ httpClientMock.VerifyAll();
+ deploymentStatusServiceMock.VerifyAll();
+ taskLoggerMock.VerifyAll();
+ }
+
+ [Theory]
+ [InlineData("z:\\Missing\\Directory\\File")]
+ [InlineData("")]
+ [InlineData(null)]
+ public async Task OneDeploy_Execute_FileToPublish_Missing(string invalidFileToPublish)
+ {
+ // Arrange
+ var httpClientMock = new Mock();
+
+ var deploymentStatusServiceMock = new Mock>();
+
+ // set messages to log
+ var taskLoggerMock = new Mock();
+ taskLoggerMock.Setup(l => l.LogError(Resources.ONEDEPLOY_FileToPublish_NotFound));
+
+ var oneDeployTask = new OneDeploy(taskLoggerMock.Object);
+
+ // Act
+ var result = await oneDeployTask.OneDeployAsync(
+ invalidFileToPublish, Username, Password, PublishUrl, $"{UserAgentName}/8.0", webjobName: null, webjobType: null, httpClientMock.Object, deploymentStatusServiceMock.Object, CancellationToken.None);
+
+ // Assert: deployment operation fails because 'FileToPublishPath' is not valid
+ Assert.False(result);
+
+ httpClientMock.VerifyAll();
+ deploymentStatusServiceMock.VerifyAll();
+ taskLoggerMock.VerifyAll();
+ }
+
+ [Theory]
+ [InlineData(Username, "")]
+ [InlineData(Username, null)]
+ [InlineData("", "")]
+ [InlineData(null, null)]
+ public async Task OneDeploy_Execute_Credentials_Missing_Args(string invalidUsername, string invalidPassword)
+ {
+ // Arrange
+ var httpClientMock = new Mock();
+
+ var deploymentStatusServiceMock = new Mock>();
+
+ // set messages to log
+ var taskLoggerMock = new Mock();
+ taskLoggerMock.Setup(l => l.LogError(Resources.ONEDEPLOY_FailedToRetrieveCredentials));
+
+ var oneDeployTask = new OneDeploy(taskLoggerMock.Object);
+
+ // Act
+ var result = await oneDeployTask.OneDeployAsync(
+ FileToPublish, invalidUsername, invalidPassword, PublishUrl, $"{UserAgentName}/8.0", webjobName: null, webjobType: null, httpClientMock.Object, deploymentStatusServiceMock.Object, CancellationToken.None);
+
+ // Assert: deployment operation fails because 'Username' and/or 'Password' is
+ // not valid nor the could be retrieved from Task HostObject
+ Assert.False(result);
+
+ httpClientMock.VerifyAll();
+ deploymentStatusServiceMock.VerifyAll();
+ taskLoggerMock.VerifyAll();
+ }
+
+ [Fact]
+ public async Task OneDeploy_Execute_Credentials_From_TaskHostObject()
+ {
+ // Arrange
+ var httpClientMock = GetHttpClientMock(HttpStatusCode.OK);
+
+ var deploymentStatusServiceMock = GetDeploymentStatusServiceMock(httpClientMock.Object, DeploymentStatus.PartialSuccess);
+
+ // set messages to log according to result
+ var taskLoggerMock = new Mock();
+ taskLoggerMock.Setup(l => l.LogMessage(Build.Framework.MessageImportance.High, string.Format(Resources.ONEDEPLOY_PublishingOneDeploy, FileToPublish, OneDeployUri.AbsoluteUri.ToString())));
+ taskLoggerMock.Setup(l => l.LogMessage(string.Format(Resources.ONEDEPLOY_Uploaded, FileToPublish)));
+ taskLoggerMock.Setup(l => l.LogMessage(Build.Framework.MessageImportance.High, Resources.ONEDEPLOY_Success));
+
+ var oneDeployTask = new OneDeploy(taskLoggerMock.Object);
+
+ var msbuildHostObject = new VSMsDeployTaskHostObject();
+ msbuildHostObject.AddCredentialTaskItemIfExists(Username, Password);
+ oneDeployTask.HostObject = msbuildHostObject;
+
+ // Act
+ var result = await oneDeployTask.OneDeployAsync(
+ FileToPublish, Username, Password, PublishUrl, $"{UserAgentName}/8.0", webjobName: null, webjobType: null, httpClientMock.Object, deploymentStatusServiceMock.Object, CancellationToken.None);
+
+ // Assert: deployment operation runs to completion
+ // obtaining the credentials from the Task HostObject
+ Assert.True(result);
+
+ httpClientMock.VerifyAll();
+ deploymentStatusServiceMock.VerifyAll();
+ taskLoggerMock.VerifyAll();
+ }
+
+ private Mock GetHttpClientMock(
+ HttpStatusCode statusCode,
+ string deployLocationHeader = DeploymentUrl)
+ {
+ var httpClientMock = new Mock();
+
+ // Request
+ HttpRequestMessage requestMessage = new();
+ httpClientMock
+ .Setup(hc => hc.DefaultRequestHeaders)
+ .Returns(requestMessage.Headers);
+
+ // Response
+ HttpResponseMessage responseMessage = new(statusCode);
+ if (!string.IsNullOrEmpty(deployLocationHeader))
+ {
+ responseMessage.Headers.Add("Location", deployLocationHeader);
+ }
+
+ // PostAsync()
+ httpClientMock
+ .Setup(hc => hc.PostAsync(OneDeployUri, It.IsAny()))
+ .ReturnsAsync(responseMessage);
+
+ return httpClientMock;
+ }
+
+ [Fact]
+ public void Constructor_OK()
+ {
+ var oneDeploy = new OneDeploy();
+
+ // no-arg ctor (as invoked by MSBuild) instantiate instance
+ Assert.NotNull(oneDeploy);
+ }
+
+ private Mock> GetDeploymentStatusServiceMock(
+ IHttpClient httpClient,
+ DeploymentStatus status = DeploymentStatus.Success,
+ string statusText = null)
+ {
+ var deploymentResponse = new DeploymentResponse()
+ {
+ Status = status,
+ StatusText = statusText,
+ LogUrl = DeploymentLogUrl
+ };
+
+ var statusServiceMock = new Mock>();
+
+ statusServiceMock
+ .Setup(s => s.PollDeploymentAsync(httpClient, DeploymentUrl, Username, Password, $"{UserAgentName}/8.0", It.IsAny()))
+ .ReturnsAsync(deploymentResponse);
+
+ return statusServiceMock;
+ }
+}