Skip to content

Commit a90271a

Browse files
authored
Fix check for IsFileSystemReadOnly (#7295)
Update logic to determine if the filesystem is read-only. It's not sufficient to check if SCM_RUN_FROM_PACKAGE is set, the URI also needs to point to an existing blob to deduce it's a zip deployment (therefore read-only).
1 parent a68d61d commit a90271a

25 files changed

+348
-167
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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+
using System;
5+
using System.Threading;
6+
using Microsoft.Azure.Storage.Blob;
7+
using Microsoft.Extensions.Options;
8+
using static Microsoft.Azure.WebJobs.Script.EnvironmentSettingNames;
9+
10+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Configuration
11+
{
12+
public class LinuxScriptApplicationHostOptionsSetup : IConfigureOptions<ScriptApplicationHostOptions>
13+
{
14+
private readonly IEnvironment _environment;
15+
private static readonly IConfigureOptions<ScriptApplicationHostOptions> _instance = new NullSetup();
16+
17+
public LinuxScriptApplicationHostOptionsSetup(IEnvironment environment)
18+
{
19+
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
20+
}
21+
22+
internal static IConfigureOptions<ScriptApplicationHostOptions> NullInstance => _instance;
23+
24+
public void Configure(ScriptApplicationHostOptions options)
25+
{
26+
options.IsFileSystemReadOnly = IsZipDeployment(options);
27+
}
28+
29+
private bool IsZipDeployment(ScriptApplicationHostOptions options)
30+
{
31+
// If the app is using app settings for run from package, we don't need to check further, it must be a zip deployment.
32+
bool runFromPkgConfigured = Utility.IsValidZipSetting(_environment.GetEnvironmentVariable(AzureWebsiteZipDeployment)) ||
33+
Utility.IsValidZipSetting(_environment.GetEnvironmentVariable(AzureWebsiteAltZipDeployment)) ||
34+
Utility.IsValidZipSetting(_environment.GetEnvironmentVariable(AzureWebsiteRunFromPackage));
35+
36+
if (runFromPkgConfigured)
37+
{
38+
return true;
39+
}
40+
41+
// If SCM_RUN_FROM_PACKAGE is set to a valid value and the blob exists, it's a zip deployment.
42+
// We need to explicitly check if the blob exists because on Linux Consumption the app setting is always added, regardless if it's used or not.
43+
var url = _environment.GetEnvironmentVariable(ScmRunFromPackage);
44+
bool scmRunFromPkgConfigured = Utility.IsValidZipSetting(url) && BlobExists(url);
45+
options.IsScmRunFromPackage = scmRunFromPkgConfigured;
46+
47+
return scmRunFromPkgConfigured;
48+
}
49+
50+
public virtual bool BlobExists(string url)
51+
{
52+
if (string.IsNullOrEmpty(url))
53+
{
54+
return false;
55+
}
56+
57+
try
58+
{
59+
CloudBlockBlob blob = new CloudBlockBlob(new Uri(url));
60+
61+
int attempt = 0;
62+
while (true)
63+
{
64+
try
65+
{
66+
return blob.Exists();
67+
}
68+
catch (Exception ex) when (!ex.IsFatal())
69+
{
70+
if (++attempt > 2)
71+
{
72+
return false;
73+
}
74+
Thread.Sleep(TimeSpan.FromSeconds(0.3));
75+
}
76+
}
77+
}
78+
catch (Exception)
79+
{
80+
return false;
81+
}
82+
}
83+
84+
private class NullSetup : IConfigureOptions<ScriptApplicationHostOptions>
85+
{
86+
public void Configure(ScriptApplicationHostOptions options)
87+
{
88+
// Do nothing.
89+
}
90+
}
91+
}
92+
}

src/WebJobs.Script.WebHost/Configuration/ScriptApplicationHostOptionsSetup.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Microsoft.Azure.WebJobs.Script.Configuration;
77
using Microsoft.Extensions.Configuration;
88
using Microsoft.Extensions.Options;
9+
using static Microsoft.Azure.WebJobs.Script.EnvironmentSettingNames;
910

1011
namespace Microsoft.Azure.WebJobs.Script.WebHost.Configuration
1112
{
@@ -17,16 +18,18 @@ public class ScriptApplicationHostOptionsSetup : IConfigureNamedOptions<ScriptAp
1718
private readonly IOptionsMonitor<StandbyOptions> _standbyOptions;
1819
private readonly IDisposable _standbyOptionsOnChangeSubscription;
1920
private readonly IServiceProvider _serviceProvider;
21+
private readonly IEnvironment _environment;
2022

2123
public ScriptApplicationHostOptionsSetup(IConfiguration configuration, IOptionsMonitor<StandbyOptions> standbyOptions,
22-
IOptionsMonitorCache<ScriptApplicationHostOptions> cache, IServiceProvider serviceProvider)
24+
IOptionsMonitorCache<ScriptApplicationHostOptions> cache, IServiceProvider serviceProvider, IEnvironment environment)
2325
{
2426
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
2527
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
2628
_standbyOptions = standbyOptions ?? throw new ArgumentNullException(nameof(standbyOptions));
2729
_serviceProvider = serviceProvider;
2830
// If standby options change, invalidate this options cache.
2931
_standbyOptionsOnChangeSubscription = _standbyOptions.OnChange(o => _cache.Clear());
32+
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
3033
}
3134

3235
public void Configure(ScriptApplicationHostOptions options)
@@ -61,6 +64,15 @@ public void Configure(string name, ScriptApplicationHostOptions options)
6164
options.IsSelfHost = options.IsSelfHost;
6265
options.IsStandbyConfiguration = true;
6366
}
67+
68+
options.IsFileSystemReadOnly = IsZipDeployment();
69+
}
70+
71+
private bool IsZipDeployment()
72+
{
73+
return Utility.IsValidZipSetting(_environment.GetEnvironmentVariable(AzureWebsiteZipDeployment)) ||
74+
Utility.IsValidZipSetting(_environment.GetEnvironmentVariable(AzureWebsiteAltZipDeployment)) ||
75+
Utility.IsValidZipSetting(_environment.GetEnvironmentVariable(AzureWebsiteRunFromPackage));
6476
}
6577

6678
public void Dispose()

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ public async Task<IActionResult> SetState([FromBody] string state)
264264
}
265265
else if (desiredState == ScriptHostState.Running && currentState == ScriptHostState.Offline)
266266
{
267-
if (_environment.IsFileSystemReadOnly())
267+
if (_applicationHostOptions.Value.IsFileSystemReadOnly)
268268
{
269269
return BadRequest();
270270
}
@@ -274,7 +274,7 @@ public async Task<IActionResult> SetState([FromBody] string state)
274274
}
275275
else if (desiredState == ScriptHostState.Offline && currentState != ScriptHostState.Offline)
276276
{
277-
if (_environment.IsFileSystemReadOnly())
277+
if (_applicationHostOptions.Value.IsFileSystemReadOnly)
278278
{
279279
return BadRequest();
280280
}

src/WebJobs.Script.WebHost/Management/InstanceManager.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ private async Task ApplyContext(HostAssignmentContext assignmentContext)
302302
$"No {nameof(EnvironmentSettingNames.AzureFilesConnectionString)} or {nameof(EnvironmentSettingNames.AzureFilesContentShare)} configured. Azure FileShare will not be mounted. For PowerShell Functions, Managed Dependencies will not persisted across functions host instances.");
303303
}
304304

305-
if (await pkgContext.IsRunFromPackage(_logger))
305+
if (pkgContext.IsRunFromPackage(options, _logger))
306306
{
307307
if (azureFilesMounted)
308308
{
@@ -325,7 +325,7 @@ await _runFromPackageHandler.ApplyBlobPackageContext(pkgContext, options.ScriptP
325325
}
326326
else
327327
{
328-
if (await pkgContext.IsRunFromPackage(_logger))
328+
if (pkgContext.IsRunFromPackage(options, _logger))
329329
{
330330
await _runFromPackageHandler.ApplyBlobPackageContext(pkgContext, options.ScriptPath, false);
331331
}

src/WebJobs.Script.WebHost/Management/LinuxSpecialization/RunFromPackageCloudBlockBlobService.cs

Lines changed: 0 additions & 43 deletions
This file was deleted.

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,14 @@
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Threading.Tasks;
6-
using Microsoft.Azure.WebJobs.Script.WebHost.Management.LinuxSpecialization;
75
using Microsoft.Extensions.Logging;
86

97
namespace Microsoft.Azure.WebJobs.Script.WebHost.Models
108
{
119
public class RunFromPackageContext
1210
{
13-
private readonly RunFromPackageCloudBlockBlobService _runFromPackageCloudBlockBlobService;
14-
15-
public RunFromPackageContext(string envVarName, string url, long? packageContentLength, bool isWarmupRequest, RunFromPackageCloudBlockBlobService runFromPackageCloudBlockBlobService = null)
11+
public RunFromPackageContext(string envVarName, string url, long? packageContentLength, bool isWarmupRequest)
1612
{
17-
_runFromPackageCloudBlockBlobService = runFromPackageCloudBlockBlobService ?? new RunFromPackageCloudBlockBlobService();
1813
EnvironmentVariableName = envVarName;
1914
Url = url;
2015
PackageContentLength = packageContentLength;
@@ -31,14 +26,19 @@ public RunFromPackageContext(string envVarName, string url, long? packageContent
3126

3227
public bool IsScmRunFromPackage()
3328
{
34-
return string.Equals(EnvironmentVariableName, EnvironmentSettingNames.ScmRunFromPackage,
35-
StringComparison.OrdinalIgnoreCase);
29+
return string.Equals(EnvironmentVariableName, EnvironmentSettingNames.ScmRunFromPackage, StringComparison.OrdinalIgnoreCase);
30+
}
31+
32+
public bool IsRunFromPackage(ScriptApplicationHostOptions options, ILogger logger)
33+
{
34+
return (IsScmRunFromPackage() && ScmRunFromPackageBlobExists(options, logger)) || (!IsScmRunFromPackage() && !string.IsNullOrEmpty(Url) && Url != "1");
3635
}
3736

38-
public async Task<bool> IsRunFromPackage(ILogger logger)
37+
private bool ScmRunFromPackageBlobExists(ScriptApplicationHostOptions options, ILogger logger)
3938
{
40-
return (IsScmRunFromPackage() && await _runFromPackageCloudBlockBlobService.BlobExists(Url, EnvironmentVariableName, logger)) ||
41-
(!IsScmRunFromPackage() && !string.IsNullOrEmpty(Url) && Url != "1");
39+
var blobExists = options.IsScmRunFromPackage;
40+
logger.LogDebug($"{EnvironmentSettingNames.ScmRunFromPackage} points to an existing blob: {blobExists}");
41+
return blobExists;
4242
}
4343
}
4444
}

src/WebJobs.Script.WebHost/WebHostServiceCollectionExtensions.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public static void AddWebJobsScriptHost(this IServiceCollection services, IConfi
9191
services.AddLinuxContainerServices();
9292

9393
// ScriptSettingsManager should be replaced. We're setting this here as a temporary step until
94-
// broader configuaration changes are made:
94+
// broader configuration changes are made:
9595
services.AddSingleton<ScriptSettingsManager>();
9696
services.AddSingleton<IEventGenerator>(p =>
9797
{
@@ -163,6 +163,15 @@ public static void AddWebJobsScriptHost(this IServiceCollection services, IConfi
163163

164164
// Configuration
165165
services.ConfigureOptions<ScriptApplicationHostOptionsSetup>();
166+
services.AddSingleton<IConfigureOptions<ScriptApplicationHostOptions>>(p =>
167+
{
168+
var environment = p.GetService<IEnvironment>();
169+
if (environment.IsLinuxConsumption())
170+
{
171+
return new LinuxScriptApplicationHostOptionsSetup(environment);
172+
}
173+
return LinuxScriptApplicationHostOptionsSetup.NullInstance;
174+
});
166175
services.ConfigureOptions<StandbyOptionsSetup>();
167176
services.ConfigureOptions<LanguageWorkerOptionsSetup>();
168177
services.ConfigureOptionsWithChangeTokenSource<AppServiceOptions, AppServiceOptionsSetup, SpecializationChangeTokenSource<AppServiceOptions>>();

src/WebJobs.Script.WebHost/WebJobsScriptHostService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ public async Task StartAsync(CancellationToken cancellationToken)
169169
private void CheckFileSystem()
170170
{
171171
// Shutdown if RunFromZipFailed
172-
if (_environment.IsZipDeployment(validate: false))
172+
if (_environment.ZipDeploymentAppSettingsExist())
173173
{
174174
string path = Path.Combine(_applicationHostOptions.CurrentValue.ScriptPath, ScriptConstants.RunFromPackageFailedFileName);
175175
if (File.Exists(path))

src/WebJobs.Script/Config/HostJsonFileConfigurationSource.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ private JObject GetDefaultHostConfigObject()
228228
{
229229
var hostJsonJObj = JObject.Parse("{'version': '2.0'}");
230230
if (string.Equals(_configurationSource.Environment.GetEnvironmentVariable(RpcWorkerConstants.FunctionWorkerRuntimeSettingName), "powershell", StringComparison.InvariantCultureIgnoreCase)
231-
&& !_configurationSource.Environment.IsFileSystemReadOnly())
231+
&& !_configurationSource.HostOptions.IsFileSystemReadOnly)
232232
{
233233
hostJsonJObj.Add("managedDependency", JToken.Parse("{'Enabled': true}"));
234234
}
@@ -238,7 +238,7 @@ private JObject GetDefaultHostConfigObject()
238238

239239
private void TryWriteHostJson(string filePath, JObject content)
240240
{
241-
if (!_configurationSource.Environment.IsFileSystemReadOnly())
241+
if (!_configurationSource.HostOptions.IsFileSystemReadOnly)
242242
{
243243
try
244244
{
@@ -257,7 +257,7 @@ private void TryWriteHostJson(string filePath, JObject content)
257257

258258
private JObject TryAddBundleConfiguration(JObject content, string bundleId)
259259
{
260-
if (!_configurationSource.Environment.IsFileSystemReadOnly())
260+
if (!_configurationSource.HostOptions.IsFileSystemReadOnly)
261261
{
262262
string bundleConfiguration = "{ 'id': '" + bundleId + "', 'version': '[2.*, 3.0.0)'}";
263263
content.Add("extensionBundle", JToken.Parse(bundleConfiguration));

src/WebJobs.Script/Config/ScriptApplicationHostOptions.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,9 @@ public class ScriptApplicationHostOptions
3131
public IServiceProvider RootServiceProvider { get; set; }
3232

3333
public bool IsStandbyConfiguration { get; internal set; }
34+
35+
public bool IsFileSystemReadOnly { get; set; }
36+
37+
public bool IsScmRunFromPackage { get; set; }
3438
}
3539
}

0 commit comments

Comments
 (0)