Skip to content

Commit 20f5e27

Browse files
authored
Setting max supported bundle version via hosting config (#9573)
1 parent c39c82a commit 20f5e27

File tree

6 files changed

+102
-9
lines changed

6 files changed

+102
-9
lines changed

src/WebJobs.Script/Config/FunctionsHostingConfigOptions.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,29 @@ public bool DisableLinuxAppServiceExecutionDetails
109109

110110
set
111111
{
112-
_features[ScriptConstants.HostingConfigDisableLinuxAppServiceDetailedExecutionEvents] = value ? "1" : "0";
112+
_features[ScriptConstants.HostingConfigDisableLinuxAppServiceDetailedExecutionEvents] = value ? "1" : "0";
113+
}
114+
}
115+
116+
/// <summary>
117+
/// Gets the highest version of extension bundle v3 supported
118+
/// </summary>
119+
public string MaximumBundleV3Version
120+
{
121+
get
122+
{
123+
return GetFeature(ScriptConstants.MaximumBundleV3Version);
124+
}
125+
}
126+
127+
/// <summary>
128+
/// Gets the highest version of extension bundle v4 supported
129+
/// </summary>
130+
public string MaximumBundleV4Version
131+
{
132+
get
133+
{
134+
return GetFeature(ScriptConstants.MaximumBundleV4Version);
113135
}
114136
}
115137

src/WebJobs.Script/ExtensionBundle/ExtensionBundleManager.cs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Net.Http;
1111
using System.Resources;
1212
using System.Threading.Tasks;
13+
using Microsoft.Azure.WebJobs.Script.Config;
1314
using Microsoft.Azure.WebJobs.Script.Configuration;
1415
using Microsoft.Azure.WebJobs.Script.Diagnostics.Extensions;
1516
using Microsoft.Azure.WebJobs.Script.Models;
@@ -23,16 +24,18 @@ public class ExtensionBundleManager : IExtensionBundleManager
2324
{
2425
private readonly IEnvironment _environment;
2526
private readonly ExtensionBundleOptions _options;
27+
private readonly FunctionsHostingConfigOptions _configOption;
2628
private readonly ILogger _logger;
2729
private readonly string _cdnUri;
2830
private string _extensionBundleVersion;
2931

30-
public ExtensionBundleManager(ExtensionBundleOptions options, IEnvironment environment, ILoggerFactory loggerFactory)
32+
public ExtensionBundleManager(ExtensionBundleOptions options, IEnvironment environment, ILoggerFactory loggerFactory, FunctionsHostingConfigOptions configOption)
3133
{
3234
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
3335
_logger = loggerFactory.CreateLogger<ExtensionBundleManager>() ?? throw new ArgumentNullException(nameof(loggerFactory));
3436
_cdnUri = _environment.GetEnvironmentVariable(EnvironmentSettingNames.ExtensionBundleSourceUri) ?? ScriptConstants.ExtensionBundleDefaultSourceUri;
3537
_options = options ?? throw new ArgumentNullException(nameof(options));
38+
_configOption = configOption ?? throw new ArgumentNullException(nameof(configOption));
3639
}
3740

3841
public async Task<ExtensionBundleDetails> GetExtensionBundleDetails()
@@ -128,7 +131,7 @@ internal bool TryLocateExtensionBundle(out string bundlePath)
128131
if (FileUtility.DirectoryExists(path))
129132
{
130133
var bundleDirectories = FileUtility.EnumerateDirectories(path);
131-
string version = FindBestVersionMatch(_options.Version, bundleDirectories);
134+
string version = FindBestVersionMatch(_options.Version, bundleDirectories, _options.Id, _configOption);
132135

133136
if (!string.IsNullOrEmpty(version))
134137
{
@@ -236,7 +239,8 @@ private async Task<string> GetLatestMatchingBundleVersionAsync(HttpClient httpCl
236239

237240
var content = await response.Content.ReadAsStringAsync();
238241
var bundleVersions = JsonConvert.DeserializeObject<IEnumerable<string>>(content);
239-
var matchingBundleVersion = FindBestVersionMatch(_options.Version, bundleVersions);
242+
243+
var matchingBundleVersion = FindBestVersionMatch(_options.Version, bundleVersions, _options.Id, _configOption);
240244

241245
if (string.IsNullOrEmpty(matchingBundleVersion))
242246
{
@@ -246,7 +250,7 @@ private async Task<string> GetLatestMatchingBundleVersionAsync(HttpClient httpCl
246250
return matchingBundleVersion;
247251
}
248252

249-
private static string FindBestVersionMatch(VersionRange versionRange, IEnumerable<string> versions)
253+
internal static string FindBestVersionMatch(VersionRange versionRange, IEnumerable<string> versions, string bundleId, FunctionsHostingConfigOptions configOption)
250254
{
251255
var bundleVersions = versions.Select(p =>
252256
{
@@ -259,7 +263,34 @@ private static string FindBestVersionMatch(VersionRange versionRange, IEnumerabl
259263
return version;
260264
}).Where(v => v != null);
261265

262-
return bundleVersions.OrderByDescending(version => version.Version).FirstOrDefault()?.ToString();
266+
var matchingVersion = bundleVersions.OrderByDescending(version => version.Version).FirstOrDefault();
267+
268+
if (bundleId != ScriptConstants.DefaultExtensionBundleId)
269+
{
270+
return matchingVersion?.ToString();
271+
}
272+
273+
// Check to see if there is a max bundle version set via hosting configuration, if yes then use that instead of the one
274+
// available on VM or local machine. Only use MaximumBundleV3Version or MaximumBundleV4Version if the version configured
275+
// by the customer resolved to version higher than the version set via hosting config.
276+
if (!string.IsNullOrEmpty(configOption.MaximumBundleV3Version)
277+
&& matchingVersion?.Major == ScriptConstants.ExtensionBundleV3MajorVersion)
278+
{
279+
var maximumBundleV3Version = NuGetVersion.Parse(configOption.MaximumBundleV3Version);
280+
matchingVersion = matchingVersion > maximumBundleV3Version ? maximumBundleV3Version : matchingVersion;
281+
return matchingVersion?.ToString();
282+
}
283+
284+
if (!string.IsNullOrEmpty(configOption.MaximumBundleV4Version)
285+
&& matchingVersion?.Major == ScriptConstants.ExtensionBundleV4MajorVersion)
286+
{
287+
var maximumBundleV4Version = NuGetVersion.Parse(configOption.MaximumBundleV4Version);
288+
matchingVersion = matchingVersion > maximumBundleV4Version
289+
? maximumBundleV4Version
290+
: matchingVersion;
291+
}
292+
293+
return matchingVersion?.ToString();
263294
}
264295

265296
public async Task<string> GetExtensionBundleBinPathAsync()

src/WebJobs.Script/ScriptConstants.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ public static class ScriptConstants
183183
public const string AzureWebJobsHostsContainerName = "azure-webjobs-hosts";
184184

185185
public const string DefaultExtensionBundleDirectory = "FuncExtensionBundles";
186+
public const int ExtensionBundleV3MajorVersion = 3;
187+
public const int ExtensionBundleV4MajorVersion = 4;
186188
public const string ExtensionBundleDirectory = "ExtensionBundles";
187189
public const string ExtensionBundleDefaultSourceUri = "https://functionscdn.azureedge.net/public";
188190
public const string ExtensionBundleMetadataFile = "bundle.json";
@@ -231,6 +233,8 @@ public static class ScriptConstants
231233
public static readonly string LiveLogsSessionAIKey = "#AzFuncLiveLogsSessionId";
232234

233235
public static readonly string FunctionsHostingConfigSectionName = "FunctionsHostingConfig";
236+
public static readonly string MaximumBundleV3Version = "FunctionRuntimeV4MaxBundleV3Version";
237+
public static readonly string MaximumBundleV4Version = "FunctionRuntimeV4MaxBundleV4Version";
234238

235239
// HTTP Proxying constants
236240
public static readonly string HttpProxyingEnabled = "HttpProxyingEnabled";

src/WebJobs.Script/ScriptHostBuilderExtensions.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ public static IHostBuilder AddScriptHost(this IHostBuilder builder,
104104
configBuilder.Add(new HostJsonFileConfigurationSource(applicationOptions, SystemEnvironment.Instance, loggerFactory, metricsLogger));
105105
}
106106
// Adding hosting config into job host configuration
107+
configBuilder.Add(new FunctionsHostingConfigSource(SystemEnvironment.Instance));
107108
IConfiguration scriptHostConfiguration = applicationOptions.RootServiceProvider.GetService<IConfiguration>();
108109
if (scriptHostConfiguration != null)
109110
{
@@ -120,7 +121,11 @@ public static IHostBuilder AddScriptHost(this IHostBuilder builder,
120121
// Pre-build configuration here to load bundles and to store for later validation.
121122
var config = configBuilder.Build();
122123
var extensionBundleOptions = GetExtensionBundleOptions(config);
123-
var bundleManager = new ExtensionBundleManager(extensionBundleOptions, SystemEnvironment.Instance, loggerFactory);
124+
FunctionsHostingConfigOptions configOption = new FunctionsHostingConfigOptions();
125+
var optionsSetup = new FunctionsHostingConfigOptionsSetup(config);
126+
optionsSetup.Configure(configOption);
127+
128+
var bundleManager = new ExtensionBundleManager(extensionBundleOptions, SystemEnvironment.Instance, loggerFactory, configOption);
124129
var metadataServiceManager = applicationOptions.RootServiceProvider.GetService<IFunctionMetadataManager>();
125130
var languageWorkerOptions = applicationOptions.RootServiceProvider.GetService<IOptionsMonitor<LanguageWorkerOptions>>();
126131

test/CSharpPrecompiledTestProjects/WebJobsStartupTests/Function1.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ private static bool ValidateConfig(IConfiguration _config)
104104
{
105105
if (_config is ConfigurationRoot root)
106106
{
107-
if (root.Providers.Count() != 8)
107+
if (root.Providers.Count() != 9)
108108
{
109109
return false;
110110
}
@@ -115,6 +115,7 @@ private static bool ValidateConfig(IConfiguration _config)
115115
root.Providers.ElementAt(i++) is ChainedConfigurationProvider &&
116116
root.Providers.ElementAt(i++) is MemoryConfigurationProvider &&
117117
root.Providers.ElementAt(i++).GetType().Name.StartsWith("HostJsonFile") &&
118+
root.Providers.ElementAt(i++).GetType().Name.StartsWith("FunctionsHostingConfigProvider") &&
118119
root.Providers.ElementAt(i++) is ChainedConfigurationProvider &&
119120
root.Providers.ElementAt(i++) is JsonConfigurationProvider &&
120121
root.Providers.ElementAt(i++) is EnvironmentVariablesConfigurationProvider &&

test/WebJobs.Script.Tests/ExtensionBundle/ExtensionBundleManagerTests.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using System.Text;
1515
using System.Threading;
1616
using System.Threading.Tasks;
17+
using Microsoft.Azure.WebJobs.Script.Config;
1718
using Microsoft.Azure.WebJobs.Script.Configuration;
1819
using Microsoft.Azure.WebJobs.Script.ExtensionBundle;
1920
using Microsoft.Extensions.Configuration;
@@ -384,10 +385,39 @@ public async Task GetExtensionBundle_CannotReachZipEndpoint_ReturnsFalseAsync()
384385
Assert.Null(await manager.GetExtensionBundlePath(httpClient));
385386
}
386387

388+
[Theory]
389+
[InlineData("[3.*, 4.0.0)", "3.19.0", "3.19.0")]
390+
[InlineData("[4.*, 5.0.0)", "4.2.0", "4.2.0")]
391+
[InlineData("[4.*, 5.0.0)", null, "4.3.0")]
392+
[InlineData("[3.*, 4.0.0)", null, "3.20.0")]
393+
public void LimitMaxVersion(string versionRange, string hostConfigVersion, string expectedVersion)
394+
{
395+
var range = VersionRange.Parse(versionRange);
396+
var hostingConfiguration = new FunctionsHostingConfigOptions();
397+
if (!string.IsNullOrEmpty(hostConfigVersion))
398+
{
399+
if (range.MinVersion.Major == 3)
400+
{
401+
hostingConfiguration.Features.Add(ScriptConstants.MaximumBundleV3Version, hostConfigVersion);
402+
}
403+
404+
if (range.MinVersion.Major == 4)
405+
{
406+
hostingConfiguration.Features.Add(ScriptConstants.MaximumBundleV4Version, hostConfigVersion);
407+
}
408+
}
409+
410+
var resolvedVersion = ExtensionBundleManager.FindBestVersionMatch(range, new List<string>()
411+
{ "3.7.0", "3.10.0", "3.11.0", "3.15.0", "3.14.0", "2.16.0", "3.13.0", "3.12.0", "3.9.1", "2.12.1", "2.18.0", "3.16.0", "2.19.0", "3.17.0", "4.0.2", "2.20.0", "3.18.0", "4.1.0", "4.2.0", "2.21.0", "3.19.0", "3.19.2", "4.3.0", "3.20.0" },
412+
ScriptConstants.DefaultExtensionBundleId, hostingConfiguration);
413+
414+
Assert.Equal(expectedVersion, resolvedVersion);
415+
}
416+
387417
private ExtensionBundleManager GetExtensionBundleManager(ExtensionBundleOptions bundleOptions, TestEnvironment environment = null)
388418
{
389419
environment = environment ?? new TestEnvironment();
390-
return new ExtensionBundleManager(bundleOptions, environment, MockNullLoggerFactory.CreateLoggerFactory());
420+
return new ExtensionBundleManager(bundleOptions, environment, MockNullLoggerFactory.CreateLoggerFactory(), new FunctionsHostingConfigOptions());
391421
}
392422

393423
private TestEnvironment GetTestAppServiceEnvironment()

0 commit comments

Comments
 (0)