1
1
using System ;
2
2
using System . Collections . Generic ;
3
+ using System . Globalization ;
3
4
using System . IO ;
4
5
using System . Linq ;
5
6
using System . Net . Http ;
10
11
using Azure . Functions . Cli . Actions . LocalActions ;
11
12
using Azure . Functions . Cli . Arm . Models ;
12
13
using Azure . Functions . Cli . Common ;
14
+ using Azure . Functions . Cli . Diagnostics ;
13
15
using Azure . Functions . Cli . Extensions ;
14
16
using Azure . Functions . Cli . Helpers ;
15
17
using Azure . Functions . Cli . Interfaces ;
18
20
using Microsoft . WindowsAzure . Storage ;
19
21
using Microsoft . WindowsAzure . Storage . Blob ;
20
22
using Newtonsoft . Json ;
23
+ using NuGet . Common ;
21
24
using static Azure . Functions . Cli . Common . OutputTheme ;
22
25
23
26
namespace Azure . Functions . Cli . Actions . AzureActions
@@ -105,6 +108,7 @@ public override ICommandLineParserResult ParseArgs(string[] args)
105
108
. Setup < string > ( "additional-packages" )
106
109
. WithDescription ( "List of packages to install when building native dependencies. For example: \" python3-dev libevent-dev\" " )
107
110
. Callback ( p => AdditionalPackages = p ) ;
111
+
108
112
Parser
109
113
. Setup < bool > ( "force" )
110
114
. WithDescription ( "Depending on the publish scenario, this will ignore pre-publish checks" )
@@ -429,7 +433,7 @@ private async Task PublishFunctionApp(Site functionApp, GitIgnoreParser ignorePa
429
433
var functionAppRoot = ScriptHostHelpers . GetFunctionAppRootDirectory ( Environment . CurrentDirectory ) ;
430
434
431
435
// For dedicated linux apps, we do not support run from package right now
432
- var isFunctionAppDedicatedLinux = functionApp . IsLinux && ! functionApp . IsDynamic && ! functionApp . IsElasticPremium ;
436
+ var isFunctionAppDedicatedLinux = functionApp . IsLinux && ! functionApp . IsDynamic && ! functionApp . IsElasticPremium && ! functionApp . IsFlex ;
433
437
434
438
if ( GlobalCoreToolsSettings . CurrentWorkerRuntime == WorkerRuntime . python && ! functionApp . IsLinux )
435
439
{
@@ -442,6 +446,7 @@ private async Task PublishFunctionApp(Site functionApp, GitIgnoreParser ignorePa
442
446
ColoredConsole . WriteLine ( WarningColor ( "Recommend using '--build remote' to resolve project dependencies remotely on Azure" ) ) ;
443
447
}
444
448
449
+ ColoredConsole . WriteLine ( GetLogMessage ( "Starting the function app deployment..." ) ) ;
445
450
Func < Task < Stream > > zipStreamFactory = ( ) => ZipHelper . GetAppZipFile ( functionAppRoot , BuildNativeDeps , PublishBuildOption ,
446
451
NoBuild , ignoreParser , AdditionalPackages , ignoreDotNetCheck : true ) ;
447
452
@@ -457,6 +462,11 @@ private async Task PublishFunctionApp(Site functionApp, GitIgnoreParser ignorePa
457
462
// Consumption Linux
458
463
shouldSyncTriggers = await HandleLinuxConsumptionPublish ( functionApp , zipStreamFactory ) ;
459
464
}
465
+ else if ( functionApp . IsFlex )
466
+ {
467
+ // Flex
468
+ shouldSyncTriggers = await HandleFlexConsumptionPublish ( functionApp , zipStreamFactory ) ;
469
+ }
460
470
else if ( functionApp . IsLinux && functionApp . IsElasticPremium )
461
471
{
462
472
// Elastic Premium Linux
@@ -496,8 +506,8 @@ private async Task PublishFunctionApp(Site functionApp, GitIgnoreParser ignorePa
496
506
{
497
507
await PublishZipDeploy ( functionApp , zipStreamFactory ) ;
498
508
}
499
-
500
- if ( shouldSyncTriggers )
509
+
510
+ if ( shouldSyncTriggers && ! functionApp . IsFlex )
501
511
{
502
512
await Task . Delay ( TimeSpan . FromSeconds ( 5 ) ) ;
503
513
await SyncTriggers ( functionApp ) ;
@@ -518,7 +528,7 @@ private async Task SyncTriggers(Site functionApp)
518
528
{
519
529
await RetryHelper . Retry ( async ( ) =>
520
530
{
521
- ColoredConsole . WriteLine ( "Syncing triggers..." ) ;
531
+ ColoredConsole . WriteLine ( GetLogMessage ( "Syncing triggers..." ) ) ;
522
532
HttpResponseMessage response = null ;
523
533
// This SyncTriggers function calls the endpoint for linux syncTriggers
524
534
response = await AzureHelper . SyncTriggers ( functionApp , AccessToken , ManagementURL ) ;
@@ -585,6 +595,7 @@ private async Task PerformAppServiceRemoteBuild(Func<Task<Stream>> zipStreamFact
585
595
functionApp . AzureAppSettings . Remove ( "WEBSITE_RUN_FROM_PACKAGE" ) ;
586
596
appSettingsUpdated = true ;
587
597
}
598
+
588
599
appSettingsUpdated = functionApp . AzureAppSettings . SafeLeftMerge ( Constants . KuduLiteDeploymentConstants . LinuxDedicatedBuildSettings ) || appSettingsUpdated ;
589
600
if ( appSettingsUpdated )
590
601
{
@@ -602,6 +613,85 @@ await WaitForAppSettingUpdateSCM(functionApp, shouldHaveSettings: functionApp.Az
602
613
await PerformServerSideBuild ( functionApp , zipStreamFactory , pollDedicatedBuild ) ;
603
614
}
604
615
616
+ /// <summary>
617
+ /// Handler for Linux Consumption publish event
618
+ /// </summary>
619
+ /// <param name="functionApp">Function App in Azure</param>
620
+ /// <param name="zipFileFactory">Factory for local project zipper</param>
621
+ /// <returns>ShouldSyncTrigger value</returns>
622
+ private async Task < bool > HandleFlexConsumptionPublish ( Site functionApp , Func < Task < Stream > > zipFileFactory )
623
+ {
624
+ Task < DeployStatus > pollDeploymentStatusTask ( HttpClient client ) => KuduLiteDeploymentHelpers . WaitForFlexDeployment ( client , functionApp ) ;
625
+ var deploymentParameters = new Dictionary < string , string > ( ) ;
626
+
627
+ if ( PublishBuildOption == BuildOption . Remote )
628
+ {
629
+ deploymentParameters . Add ( "RemoteBuild" , true . ToString ( ) ) ;
630
+ }
631
+
632
+ var deploymentStatus = await PerformFlexDeployment ( functionApp , zipFileFactory , pollDeploymentStatusTask , deploymentParameters ) ;
633
+
634
+ return deploymentStatus == DeployStatus . Success ;
635
+ }
636
+
637
+ public async Task < DeployStatus > PerformFlexDeployment ( Site functionApp , Func < Task < Stream > > zipFileFactory , Func < HttpClient , Task < DeployStatus > > deploymentStatusPollTask , IDictionary < string , string > deploymentParameters )
638
+ {
639
+ using ( var handler = new ProgressMessageHandler ( new HttpClientHandler ( ) ) )
640
+ using ( var client = GetRemoteZipClient ( functionApp , handler ) )
641
+ using ( var request = new HttpRequestMessage ( HttpMethod . Post , new Uri (
642
+ $ "api/Deploy/Zip?isAsync=true&author={ Environment . MachineName } &Deployer=core_tools&{ string . Join ( "&" , deploymentParameters ? . Select ( kvp => $ "{ kvp . Key } ={ kvp . Value } ") ) ?? string . Empty } ", UriKind . Relative ) ) )
643
+ {
644
+ ColoredConsole . WriteLine ( GetLogMessage ( "Creating archive for current directory..." ) ) ;
645
+
646
+ request . Headers . IfMatch . Add ( EntityTagHeaderValue . Any ) ;
647
+
648
+ ( var content , var length ) = CreateStreamContentZip ( await zipFileFactory ( ) ) ;
649
+ request . Content = content ;
650
+ HttpResponseMessage response = await PublishHelper . InvokeLongRunningRequest ( client , handler , request , length , "Uploading" ) ;
651
+ await PublishHelper . CheckResponseStatusAsync ( response , GetLogMessage ( "Uploading archive..." ) ) ;
652
+
653
+ // Streaming deployment status for Linux Server Side Build
654
+ DeployStatus status = await deploymentStatusPollTask ( client ) ;
655
+
656
+ if ( status == DeployStatus . Success )
657
+ {
658
+ // the deployment was successful. Waiting for 60 seconds so that Kudu finishes the sync trigger.
659
+ await Task . Delay ( TimeSpan . FromSeconds ( 60 ) ) ;
660
+
661
+ // Checking the function app host status
662
+ try
663
+ {
664
+ await AzureHelper . CheckFunctionHostStatusForFlex ( functionApp , AccessToken , ManagementURL ) ;
665
+ }
666
+ catch ( Exception )
667
+ {
668
+ throw new CliException ( "Deployment was successful but the app appears to be unhealthy, please check the app logs." ) ;
669
+ }
670
+
671
+ ColoredConsole . WriteLine ( VerboseColor ( GetLogMessage ( "The deployment was successful!" ) ) ) ;
672
+ }
673
+ else if ( status == DeployStatus . Failed )
674
+ {
675
+ throw new CliException ( "The deployment failed, Please check the printed logs." ) ;
676
+ }
677
+ else if ( status == DeployStatus . Conflict )
678
+ {
679
+ throw new CliException ( "Deployment was cancelled, another deployment in progress." ) ;
680
+ }
681
+ else if ( status == DeployStatus . PartialSuccess )
682
+ {
683
+ ColoredConsole . WriteLine ( WarningColor ( GetLogMessage ( "\" Deployment was partially successful, Please check the printed logs." ) ) ) ;
684
+ }
685
+ else if ( status == DeployStatus . Unknown )
686
+ {
687
+ ColoredConsole . WriteLine ( WarningColor ( GetLogMessage ( $ "Failed to retrieve deployment status, please visit https://{ functionApp . ScmUri } /api/deployments") ) ) ;
688
+ }
689
+
690
+ return status ;
691
+ }
692
+ }
693
+
694
+
605
695
/// <summary>
606
696
/// Handler for Linux Consumption publish event
607
697
/// </summary>
@@ -1069,6 +1159,7 @@ private static (StreamContent, long) CreateStreamContentZip(Stream zipFile)
1069
1159
private HttpClient GetRemoteZipClient ( Site functionApp , HttpMessageHandler handler = null )
1070
1160
{
1071
1161
handler = handler ?? new HttpClientHandler ( ) ;
1162
+
1072
1163
var client = new HttpClient ( handler )
1073
1164
{
1074
1165
BaseAddress = new Uri ( $ "https://{ functionApp . ScmUri } ") ,
@@ -1109,6 +1200,16 @@ private static string NormalizeDotnetFrameworkVersion(string version)
1109
1200
return $ "{ parsedVersion . Major } .{ parsedVersion . Minor } ";
1110
1201
}
1111
1202
1203
+ private string GetLogMessage ( string message )
1204
+ {
1205
+ return GetLogPrefix ( ) + message ;
1206
+ }
1207
+
1208
+ private string GetLogPrefix ( )
1209
+ {
1210
+ return $ "[{ DateTime . UtcNow . ToString ( "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffZ" , CultureInfo . InvariantCulture ) } ] ". ToString ( ) ;
1211
+ }
1212
+
1112
1213
// For testing
1113
1214
internal class AzureHelperService
1114
1215
{
0 commit comments