Skip to content

Commit 9fb0710

Browse files
authored
Cherry-pick #7842 for hotfix release and increase patch version to 3.4.1 (#7853)
* Expose if function app is editable in /admin/host/status API (#7842) * update patch version
1 parent fba87ae commit 9fb0710

File tree

10 files changed

+146
-7
lines changed

10 files changed

+146
-7
lines changed

build/common.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<LangVersion>7.2</LangVersion>
66
<MajorVersion>3</MajorVersion>
77
<MinorVersion>4</MinorVersion>
8-
<PatchVersion>0</PatchVersion>
8+
<PatchVersion>1</PatchVersion>
99
<VersionPrefix>$(MajorVersion).$(MinorVersion).$(PatchVersion)</VersionPrefix>
1010
<Version Condition=" '$(BuildNumber)' != '' ">$(VersionPrefix)-$(BuildNumber)</Version>
1111
<Version Condition=" '$(Version)' == '' ">$(VersionPrefix)</Version>

src/WebJobs.Script.WebHost/Controllers/HostController.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ public async Task<IActionResult> GetHostStatus([FromServices] IScriptHostManager
7373
InstanceId = _environment.GetInstanceId(),
7474
ComputerName = _environment.GetAntaresComputerName(),
7575
Id = await hostIdProvider.GetHostIdAsync(CancellationToken.None),
76-
ProcessUptime = (long)(DateTime.UtcNow - Process.GetCurrentProcess().StartTime).TotalMilliseconds
76+
ProcessUptime = (long)(DateTime.UtcNow - Process.GetCurrentProcess().StartTime).TotalMilliseconds,
77+
FunctionAppContentEditingState = Utility.GetFunctionAppContentEditingState(_environment, _applicationHostOptions)
7778
};
7879

7980
var bundleManager = serviceProvider.GetService<IExtensionBundleManager>();

src/WebJobs.Script.WebHost/Models/HostStatus.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
using System;
55
using System.Collections.ObjectModel;
6+
using Microsoft.Azure.WebJobs.Script.Models;
67
using Newtonsoft.Json;
8+
using Newtonsoft.Json.Converters;
79

810
namespace Microsoft.Azure.WebJobs.Script.WebHost.Models
911
{
@@ -63,6 +65,14 @@ public class HostStatus
6365
[JsonProperty(PropertyName = "processUptime", DefaultValueHandling = DefaultValueHandling.Ignore)]
6466
public long ProcessUptime { get; set; }
6567

68+
/// <summary>
69+
/// Gets or sets a value indicating function app content editing state.
70+
/// For example, if an app is running from loose files, not from zip, then it can be edited.
71+
/// </summary>
72+
[JsonProperty(PropertyName = "functionAppContentEditingState", DefaultValueHandling = DefaultValueHandling.Include)]
73+
[JsonConverter(typeof(StringEnumConverter))]
74+
public FunctionAppContentEditingState FunctionAppContentEditingState { get; set; }
75+
6676
/// <summary>
6777
/// Gets or sets the information related to Extension bundles
6878
/// </summary>

src/WebJobs.Script/Environment/EnvironmentExtensions.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
using System.IO;
77
using System.Linq;
88
using System.Runtime.InteropServices;
9+
using Microsoft.Azure.WebJobs.Script.Models;
910
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
11+
using Microsoft.Extensions.Options;
1012
using static Microsoft.Azure.WebJobs.Script.EnvironmentSettingNames;
1113

1214
namespace Microsoft.Azure.WebJobs.Script
@@ -86,6 +88,12 @@ public static bool ZipDeploymentAppSettingsExist(this IEnvironment environment)
8688
!string.IsNullOrEmpty(environment.GetEnvironmentVariable(ScmRunFromPackage));
8789
}
8890

91+
public static bool AzureFilesAppSettingsExist(this IEnvironment environment)
92+
{
93+
return !string.IsNullOrEmpty(environment.GetEnvironmentVariable(AzureFilesConnectionString)) &&
94+
!string.IsNullOrEmpty(environment.GetEnvironmentVariable(AzureFilesContentShare));
95+
}
96+
8997
public static bool IsCoreTools(this IEnvironment environment)
9098
{
9199
return !string.IsNullOrEmpty(environment.GetEnvironmentVariable(CoreToolsEnvironment));
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.Azure.WebJobs.Script.Models
5+
{
6+
public enum FunctionAppContentEditingState
7+
{
8+
/// <summary>
9+
/// Host cannot determine if function app content is editable
10+
/// </summary>
11+
Unknown,
12+
13+
/// <summary>
14+
/// Function app content is editable
15+
/// </summary>
16+
Allowed,
17+
18+
/// <summary>
19+
/// Function app content is not editable
20+
/// </summary>
21+
NotAllowed
22+
}
23+
}

src/WebJobs.Script/Utility.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@
2121
using Microsoft.Azure.WebJobs.Logging;
2222
using Microsoft.Azure.WebJobs.Script.Description;
2323
using Microsoft.Azure.WebJobs.Script.Diagnostics.Extensions;
24+
using Microsoft.Azure.WebJobs.Script.Models;
2425
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
2526
using Microsoft.Extensions.DependencyInjection;
2627
using Microsoft.Extensions.Logging;
28+
using Microsoft.Extensions.Options;
2729
using Newtonsoft.Json;
2830
using Newtonsoft.Json.Converters;
2931
using Newtonsoft.Json.Linq;
@@ -912,6 +914,23 @@ public static bool IsValidZipUrl(string appSetting)
912914
return Uri.TryCreate(appSetting, UriKind.Absolute, out Uri result);
913915
}
914916

917+
public static FunctionAppContentEditingState GetFunctionAppContentEditingState(IEnvironment environment, IOptions<ScriptApplicationHostOptions> applicationHostOptions)
918+
{
919+
// For now, host can determine with certainty if contents are editable only for Linux Consumption apps. Return unknown for other SKUs.
920+
if (!environment.IsLinuxConsumption())
921+
{
922+
return FunctionAppContentEditingState.Unknown;
923+
}
924+
if (!applicationHostOptions.Value.IsFileSystemReadOnly && environment.AzureFilesAppSettingsExist())
925+
{
926+
return FunctionAppContentEditingState.Allowed;
927+
}
928+
else
929+
{
930+
return FunctionAppContentEditingState.NotAllowed;
931+
}
932+
}
933+
915934
private class FilteredExpandoObjectConverter : ExpandoObjectConverter
916935
{
917936
public override bool CanWrite => true;

test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/SamplesEndToEndTests_CSharp.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ public async Task HostStatus_AdminLevel_Succeeds()
364364
string content = await response.Content.ReadAsStringAsync();
365365
JObject jsonContent = JObject.Parse(content);
366366

367-
Assert.Equal(8, jsonContent.Properties().Count());
367+
Assert.Equal(9, jsonContent.Properties().Count());
368368
AssemblyFileVersionAttribute fileVersionAttr = typeof(HostStatus).Assembly.GetCustomAttribute<AssemblyFileVersionAttribute>();
369369
Assert.True(((string)jsonContent["id"]).Length > 0);
370370
string expectedVersion = fileVersionAttr.Version;

test/WebJobs.Script.Tests/Controllers/Admin/HostControllerTests.cs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.ObjectModel;
66
using System.IO;
77
using System.Net;
8+
using System.Threading;
89
using System.Threading.Tasks;
910
using FluentAssertions;
1011
using Microsoft.AspNetCore.Http;
@@ -13,6 +14,7 @@
1314
using Microsoft.Azure.WebJobs.Host.Executors;
1415
using Microsoft.Azure.WebJobs.Host.Scale;
1516
using Microsoft.Azure.WebJobs.Script.ExtensionBundle;
17+
using Microsoft.Azure.WebJobs.Script.Models;
1618
using Microsoft.Azure.WebJobs.Script.Scale;
1719
using Microsoft.Azure.WebJobs.Script.WebHost.Controllers;
1820
using Microsoft.Azure.WebJobs.Script.WebHost.Management;
@@ -36,13 +38,15 @@ public class HostControllerTests
3638
private readonly Mock<IExtensionBundleManager> _extensionBundleManager;
3739
private readonly Mock<HostPerformanceManager> _mockHostPerformanceManager;
3840
private readonly HostHealthMonitorOptions _hostHealthMonitorOptions;
41+
private readonly ScriptApplicationHostOptions _applicationHostOptions;
3942

4043
public HostControllerTests()
4144
{
4245
_scriptPath = Path.GetTempPath();
43-
var applicationHostOptions = new ScriptApplicationHostOptions();
44-
applicationHostOptions.ScriptPath = _scriptPath;
45-
var optionsWrapper = new OptionsWrapper<ScriptApplicationHostOptions>(applicationHostOptions);
46+
_applicationHostOptions = new ScriptApplicationHostOptions();
47+
_applicationHostOptions.ScriptPath = _scriptPath;
48+
var optionsWrapper = new OptionsWrapper<ScriptApplicationHostOptions>(_applicationHostOptions);
49+
4650
var loggerProvider = new TestLoggerProvider();
4751
var loggerFactory = new LoggerFactory();
4852
loggerFactory.AddProvider(loggerProvider);
@@ -66,6 +70,37 @@ public HostControllerTests()
6670
}
6771
}
6872

73+
[Theory]
74+
[InlineData(false, false, FunctionAppContentEditingState.NotAllowed)]
75+
[InlineData(false, true, FunctionAppContentEditingState.Allowed)]
76+
[InlineData(true, true, FunctionAppContentEditingState.NotAllowed)]
77+
[InlineData(true, false, FunctionAppContentEditingState.NotAllowed)]
78+
[InlineData(true, true, FunctionAppContentEditingState.Unknown, false)]
79+
public async Task GetHostStatus_TestFunctionAppContentEditable(bool isFileSystemReadOnly, bool azureFilesAppSettingsExist, FunctionAppContentEditingState isFunctionAppContentEditable, bool isLinuxConsumption = true)
80+
{
81+
_mockScriptHostManager.SetupGet(p => p.LastError).Returns((Exception)null);
82+
var mockHostIdProvider = new Mock<IHostIdProvider>(MockBehavior.Strict);
83+
mockHostIdProvider.Setup(p => p.GetHostIdAsync(CancellationToken.None)).ReturnsAsync("test123");
84+
var mockserviceProvider = new Mock<IServiceProvider>(MockBehavior.Strict);
85+
mockserviceProvider.Setup(p => p.GetService(typeof(IExtensionBundleManager))).Returns(null);
86+
87+
if (isLinuxConsumption)
88+
{
89+
_mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.ContainerName)).Returns("test-container");
90+
}
91+
92+
_applicationHostOptions.IsFileSystemReadOnly = isFileSystemReadOnly;
93+
if (azureFilesAppSettingsExist)
94+
{
95+
_mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.AzureFilesConnectionString)).Returns("test value");
96+
_mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.AzureFilesContentShare)).Returns("test value");
97+
}
98+
99+
var result = (OkObjectResult)(await _hostController.GetHostStatus(_mockScriptHostManager.Object, mockHostIdProvider.Object, mockserviceProvider.Object));
100+
var status = (HostStatus)result.Value;
101+
Assert.Equal(status.FunctionAppContentEditingState, isFunctionAppContentEditable);
102+
}
103+
69104
[Theory]
70105
[InlineData("blah", ScriptHostState.Running, HttpStatusCode.BadRequest)]
71106
[InlineData("Stopped", ScriptHostState.Running, HttpStatusCode.BadRequest)]

test/WebJobs.Script.Tests/Extensions/EnvironmentExtensionsTests.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,5 +265,18 @@ public void IsWorkerDynamicConcurrencyEnabled_ReturnsExpectedResult(string concu
265265
environment.SetEnvironmentVariable(RpcWorkerConstants.FunctionsWorkerProcessCountSettingName, processCountValue);
266266
Assert.Equal(expected, environment.IsWorkerDynamicConcurrencyEnabled());
267267
}
268+
269+
[Theory]
270+
[InlineData(null, null, false)]
271+
[InlineData(null, "test", false)]
272+
[InlineData("test", null, false)]
273+
[InlineData("test", "test", true)]
274+
public void AzureFilesAppSettingsExist_ReturnsExpectedResult(string connectionString, string contentShare, bool expected)
275+
{
276+
var environment = new TestEnvironment();
277+
environment.SetEnvironmentVariable(AzureFilesConnectionString, connectionString);
278+
environment.SetEnvironmentVariable(AzureFilesContentShare, contentShare);
279+
Assert.Equal(expected, environment.AzureFilesAppSettingsExist());
280+
}
268281
}
269282
}

test/WebJobs.Script.Tests/UtilityTests.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System;
@@ -13,7 +13,9 @@
1313
using System.Threading.Tasks;
1414
using Microsoft.Azure.WebJobs.Logging;
1515
using Microsoft.Azure.WebJobs.Script.Description;
16+
using Microsoft.Azure.WebJobs.Script.Models;
1617
using Microsoft.Extensions.Logging;
18+
using Microsoft.Extensions.Options;
1719
using Microsoft.WebJobs.Script.Tests;
1820
using Moq;
1921
using Newtonsoft.Json.Linq;
@@ -716,6 +718,34 @@ public void IsCodelessDotNetLanguageFunction_Returns_Expected(bool setIsCodeless
716718
}
717719
}
718720

721+
[Theory]
722+
[InlineData(false, false, FunctionAppContentEditingState.NotAllowed)]
723+
[InlineData(false, true, FunctionAppContentEditingState.Allowed)]
724+
[InlineData(true, true, FunctionAppContentEditingState.NotAllowed)]
725+
[InlineData(true, false, FunctionAppContentEditingState.NotAllowed)]
726+
[InlineData(true, true, FunctionAppContentEditingState.Unknown, false)]
727+
public void GetFunctionAppContentEditingState_Returns_Expected(bool isFileSystemReadOnly, bool azureFilesAppSettingsExist, FunctionAppContentEditingState isFunctionAppContentEditable, bool isLinuxConsumption = true)
728+
{
729+
var environment = new TestEnvironment();
730+
if (isLinuxConsumption)
731+
{
732+
environment.SetEnvironmentVariable(EnvironmentSettingNames.ContainerName, "test-container");
733+
}
734+
if (azureFilesAppSettingsExist)
735+
{
736+
environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureFilesConnectionString, "test value");
737+
environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureFilesContentShare, "test value");
738+
}
739+
740+
var applicationHostOptions = new ScriptApplicationHostOptions
741+
{
742+
IsFileSystemReadOnly = isFileSystemReadOnly
743+
};
744+
var optionsWrapper = new OptionsWrapper<ScriptApplicationHostOptions>(applicationHostOptions);
745+
746+
Assert.Equal(Utility.GetFunctionAppContentEditingState(environment, optionsWrapper), isFunctionAppContentEditable);
747+
}
748+
719749
private static void VerifyLogLevel(IList<LogMessage> allLogs, string msg, LogLevel expectedLevel)
720750
{
721751
var message = allLogs.Where(l => l.FormattedMessage.Contains(msg)).FirstOrDefault();

0 commit comments

Comments
 (0)