Skip to content

Commit f5eeaa8

Browse files
soninarenbrettsam
authored andcommitted
Added API to load templates and binding metadata from extensionBundle (#4270)
1 parent 91870fd commit f5eeaa8

File tree

12 files changed

+423
-29
lines changed

12 files changed

+423
-29
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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.Collections.Generic;
6+
using System.IO;
7+
using System.Linq;
8+
using System.Threading.Tasks;
9+
using Microsoft.AspNetCore.Authorization;
10+
using Microsoft.AspNetCore.Http;
11+
using Microsoft.AspNetCore.Mvc;
12+
using Microsoft.Azure.WebJobs.Script.BindingExtensions;
13+
using Microsoft.Azure.WebJobs.Script.Config;
14+
using Microsoft.Azure.WebJobs.Script.ExtensionBundle;
15+
using Microsoft.Azure.WebJobs.Script.Models;
16+
using Microsoft.Azure.WebJobs.Script.Properties;
17+
using Microsoft.Azure.WebJobs.Script.WebHost.Models;
18+
using Microsoft.Azure.WebJobs.Script.WebHost.Security.Authorization.Policies;
19+
using Microsoft.Extensions.Logging;
20+
using Newtonsoft.Json;
21+
using Newtonsoft.Json.Linq;
22+
23+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Controllers
24+
{
25+
[Authorize(Policy = PolicyNames.AdminAuthLevel)]
26+
public class ExtensionBundleController : Controller
27+
{
28+
private readonly IExtensionBundleContentProvider _extensionBundleContent;
29+
30+
public ExtensionBundleController(IExtensionBundleContentProvider extensionBundleContent)
31+
{
32+
_extensionBundleContent = extensionBundleContent ?? throw new ArgumentNullException(nameof(extensionBundleContent));
33+
}
34+
35+
[HttpGet]
36+
[Route("admin/host/extensionBundle/v1/templates")]
37+
public async Task<IActionResult> GetTemplates()
38+
{
39+
string templates = await _extensionBundleContent.GetTemplates();
40+
41+
if (string.IsNullOrEmpty(templates))
42+
{
43+
return NotFound(Resources.ExtensionBundleTemplatesNotFound);
44+
}
45+
46+
var json = JsonConvert.DeserializeObject(templates);
47+
return Ok(json);
48+
}
49+
50+
[HttpGet]
51+
[Route("admin/host/extensionBundle/v1/bindings")]
52+
public async Task<IActionResult> GetBindings()
53+
{
54+
string bindings = await _extensionBundleContent.GetBindings();
55+
56+
if (string.IsNullOrEmpty(bindings))
57+
{
58+
return NotFound(Resources.ExtensionBundleBindingMetadataNotFound);
59+
}
60+
61+
var json = JsonConvert.DeserializeObject(bindings);
62+
return Ok(json);
63+
}
64+
65+
[HttpGet]
66+
[Route("admin/host/extensionBundle/v1/resources")]
67+
public async Task<IActionResult> GetResources()
68+
{
69+
string resources = await _extensionBundleContent.GetResources();
70+
71+
if (string.IsNullOrEmpty(resources))
72+
{
73+
return NotFound(Resources.ExtensionBundleResourcesNotFound);
74+
}
75+
76+
var json = JsonConvert.DeserializeObject(resources);
77+
return Ok(json);
78+
}
79+
80+
[HttpGet]
81+
[Route("admin/host/extensionBundle/v1/resources.{locale}")]
82+
public async Task<IActionResult> GetResourcesLocale(string locale)
83+
{
84+
string resourceFileName = $"Resources.{locale}.json";
85+
var resources = await _extensionBundleContent.GetResources(resourceFileName);
86+
87+
if (string.IsNullOrEmpty(resources))
88+
{
89+
return NotFound(string.Format(Resources.ExtensionBundleResourcesLocaleNotFound, locale));
90+
}
91+
92+
var json = JsonConvert.DeserializeObject(resources);
93+
return Ok(json);
94+
}
95+
}
96+
}

src/WebJobs.Script/Config/ExtensionBundleConfigurationHelper.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@ public class ExtensionBundleConfigurationHelper
1515
{
1616
private readonly IConfiguration _configuration;
1717
private readonly IEnvironment _environment;
18-
private readonly IHostingEnvironment _hostingEnvironment;
1918

20-
public ExtensionBundleConfigurationHelper(IConfiguration configuration, IEnvironment environment, IHostingEnvironment hostingEnvironment)
19+
public ExtensionBundleConfigurationHelper(IConfiguration configuration, IEnvironment environment)
2120
{
2221
_configuration = configuration;
2322
_environment = environment;
24-
_hostingEnvironment = hostingEnvironment;
2523
}
2624

2725
public void Configure(ExtensionBundleOptions options)
@@ -35,7 +33,7 @@ public void Configure(ExtensionBundleOptions options)
3533
ValidateBundleId(options.Id);
3634
ConfigureBundleVersion(extensionBundleSection, options);
3735

38-
if (_environment.IsAppServiceEnvironment() || _hostingEnvironment.IsDevelopment())
36+
if (_environment.IsAppServiceEnvironment())
3937
{
4038
options.DownloadPath = Path.Combine(_environment.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteHomePath),
4139
"data", "Functions", ScriptConstants.ExtensionBundleDirectory, options.Id);
@@ -66,7 +64,7 @@ private void ValidateBundleId(string id)
6664

6765
private void ConfigureProbingPaths(ExtensionBundleOptions options)
6866
{
69-
if (_environment.IsAppServiceWindowsEnvironment() || _hostingEnvironment.IsDevelopment())
67+
if (_environment.IsAppServiceWindowsEnvironment())
7068
{
7169
string windowsDefaultPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
7270
ScriptConstants.DefaultExtensionBundleDirectory,
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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.IO;
6+
using System.Threading.Tasks;
7+
using Microsoft.Azure.WebJobs.Script.Properties;
8+
using Microsoft.Extensions.Logging;
9+
10+
namespace Microsoft.Azure.WebJobs.Script.ExtensionBundle
11+
{
12+
public class ExtensionBundleContentProvider : IExtensionBundleContentProvider
13+
{
14+
private readonly IExtensionBundleManager _extensionBundleManager;
15+
private readonly ILogger _logger;
16+
17+
public ExtensionBundleContentProvider(IExtensionBundleManager extensionBundleManager, ILogger<ExtensionBundleContentProvider> logger)
18+
{
19+
_extensionBundleManager = extensionBundleManager;
20+
_logger = logger;
21+
}
22+
23+
public async Task<string> GetTemplates() => await GetFileContent(Path.Combine("StaticContent", "v1", "templates", ScriptConstants.ExtensionBundleTemplatesFile));
24+
25+
public async Task<string> GetBindings() => await GetFileContent(Path.Combine("StaticContent", "v1", "bindings", ScriptConstants.ExtensionBundleBindingMetadataFile));
26+
27+
public async Task<string> GetResources(string fileName = null) => await GetFileContent(Path.Combine("StaticContent", "v1", "resources", fileName ?? ScriptConstants.ExtensionBundleResourcesFile));
28+
29+
public async Task<string> GetFileContent(string path)
30+
{
31+
if (!_extensionBundleManager.IsExtensionBundleConfigured())
32+
{
33+
_logger.LogInformation(Resources.ExtensionBundleNotConfigured, path);
34+
return null;
35+
}
36+
37+
var bundlePath = await _extensionBundleManager.GetExtensionBundlePath();
38+
string contentFilePath = Path.Combine(bundlePath, path);
39+
40+
if (FileUtility.FileExists(contentFilePath))
41+
{
42+
return await FileUtility.ReadAsync(contentFilePath);
43+
}
44+
else
45+
{
46+
_logger.LogInformation(Resources.FileNotFound, contentFilePath);
47+
return null;
48+
}
49+
}
50+
}
51+
}

src/WebJobs.Script/ExtensionBundle/ExtensionBundleManager.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ private async Task<string> GetBundle(HttpClient httpClient)
7272
bool bundleFound = TryLocateExtensionBundle(out string bundlePath);
7373
string bundleVersion = Path.GetFileName(bundlePath);
7474

75-
if (_environment.IsPersistentFileSystemAvailable()
75+
if ((_environment.IsAppServiceEnvironment() || _environment.IsCoreToolsEnvironment())
7676
&& (!bundleFound || (Version.Parse(bundleVersion) < Version.Parse(latestBundleVersion) && _options.EnsureLatest)))
7777
{
7878
bundlePath = await DownloadExtensionBundleAsync(latestBundleVersion, httpClient);
@@ -101,7 +101,7 @@ internal bool TryLocateExtensionBundle(out string bundlePath)
101101
if (!string.IsNullOrEmpty(version))
102102
{
103103
bundlePath = Path.Combine(path, version);
104-
bundleMetatdataFile = Path.Combine(bundlePath, ScriptConstants.ExtensionBundleMetadatFile);
104+
bundleMetatdataFile = Path.Combine(bundlePath, ScriptConstants.ExtensionBundleMetadataFile);
105105
if (!string.IsNullOrEmpty(bundleMetatdataFile) && FileUtility.FileExists(bundleMetatdataFile))
106106
{
107107
_logger.LogInformation(Resources.ExtensionBundleFound, bundlePath);
@@ -118,8 +118,8 @@ private async Task<string> DownloadExtensionBundleAsync(string version, HttpClie
118118
string zipDirectoryPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
119119
FileUtility.EnsureDirectoryExists(zipDirectoryPath);
120120

121-
string zipFilePath = Path.Combine(zipDirectoryPath, $"{version}.zip");
122-
var zipUri = new Uri($"{_cdnUri}/{ScriptConstants.ExtensionBundleDirectory}/{_options.Id}/{version}/{version}.zip");
121+
string zipFilePath = Path.Combine(zipDirectoryPath, $"{_options.Id}.{version}.zip");
122+
var zipUri = new Uri($"{_cdnUri}/{ScriptConstants.ExtensionBundleDirectory}/{_options.Id}/{version}/{_options.Id}.{version}.zip");
123123

124124
string bundleMetatdataFile = null;
125125
string bundlePath = null;
@@ -132,7 +132,7 @@ private async Task<string> DownloadExtensionBundleAsync(string version, HttpClie
132132
ZipFile.ExtractToDirectory(zipFilePath, bundlePath);
133133
_logger.LogInformation(Resources.ZipExtractionComplete);
134134

135-
bundleMetatdataFile = Path.Combine(_options.DownloadPath, version, ScriptConstants.ExtensionBundleMetadatFile);
135+
bundleMetatdataFile = Path.Combine(_options.DownloadPath, version, ScriptConstants.ExtensionBundleMetadataFile);
136136
}
137137
return FileUtility.FileExists(bundleMetatdataFile) ? bundlePath : null;
138138
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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.Threading.Tasks;
5+
6+
namespace Microsoft.Azure.WebJobs.Script.ExtensionBundle
7+
{
8+
public interface IExtensionBundleContentProvider
9+
{
10+
Task<string> GetTemplates();
11+
12+
Task<string> GetBindings();
13+
14+
Task<string> GetResources(string fileName = null);
15+
}
16+
}

src/WebJobs.Script/Properties/Resources.Designer.cs

Lines changed: 55 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/WebJobs.Script/Properties/Resources.resx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,22 @@
180180
<data name="ErrorInstallingExtension" xml:space="preserve">
181181
<value>Cannot install an extension. Persistent file system not available in the current hosting environment</value>
182182
</data>
183+
<data name="ExtensionBundleBindingMetadataNotFound" xml:space="preserve">
184+
<value>Binding metadata not found within the extension bundle or extension bundle is not configured for the function app.</value>
185+
</data>
186+
<data name="ExtensionBundleNotConfigured" xml:space="preserve">
187+
<value>Extension bundle configuration is not present in host.json. Cannot load content for file {0} content.</value>
188+
</data>
189+
<data name="ExtensionBundleResourcesLocaleNotFound" xml:space="preserve">
190+
<value>Resources.{0} not found within the extension bundle or extension bundle is not configured for the function app.</value>
191+
</data>
192+
<data name="ExtensionBundleResourcesNotFound" xml:space="preserve">
193+
<value>Resources metadata not found within the extension bundle or extension bundle is not configured for the function app.</value>
194+
</data>
195+
<data name="ExtensionBundleTemplatesNotFound" xml:space="preserve">
196+
<value>Templates not found within the extension bundle or extension bundle is not configured for the function app.</value>
197+
</data>
198+
<data name="FileNotFound" xml:space="preserve">
199+
<value>File not found at {0}.</value>
200+
</data>
183201
</root>

src/WebJobs.Script/ScriptConstants.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,11 @@ public static class ScriptConstants
125125
public const string DefaultExtensionBundleDirectory = "FuncExtensionBundles";
126126
public const string ExtensionBundleDirectory = "ExtensionBundles";
127127
public const string ExtensionBundleDefaultSourceUri = "https://functionscdn.azureedge.net/public";
128-
public const string ExtensionBundleMetadatFile = "bundle.json";
128+
public const string ExtensionBundleMetadataFile = "bundle.json";
129129
public const string ExtensionBundleVersionIndexFile = "index.json";
130+
public const string ExtensionBundleBindingMetadataFile = "bindings.json";
131+
public const string ExtensionBundleTemplatesFile = "templates.json";
132+
public const string ExtensionBundleResourcesFile = "Resources.json";
130133

131134
public static readonly ImmutableArray<string> HttpMethods = ImmutableArray.Create("get", "post", "delete", "head", "patch", "put", "options");
132135
public static readonly ImmutableArray<string> AssemblyFileTypes = ImmutableArray.Create(".dll", ".exe");

src/WebJobs.Script/ScriptHostBuilderExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ public static IHostBuilder AddScriptHostCore(this IHostBuilder builder, ScriptAp
133133
services.AddTransient<IExtensionsManager, ExtensionsManager>();
134134
services.TryAddSingleton<IMetricsLogger, MetricsLogger>();
135135
services.TryAddSingleton<IScriptJobHostEnvironment, ConsoleScriptJobHostEnvironment>();
136+
services.AddTransient<IExtensionBundleContentProvider, ExtensionBundleContentProvider>();
136137

137138
// Script binding providers
138139
services.TryAddEnumerable(ServiceDescriptor.Singleton<IScriptBindingProvider, WebJobsCoreScriptBindingProvider>());
@@ -266,7 +267,7 @@ internal static void ConfigureApplicationInsights(HostBuilderContext context, IL
266267
internal static ExtensionBundleOptions GetExtensionBundleOptions(HostBuilderContext context)
267268
{
268269
var options = new ExtensionBundleOptions();
269-
var optionsSetup = new ExtensionBundleConfigurationHelper(context.Configuration, SystemEnvironment.Instance, context.HostingEnvironment);
270+
var optionsSetup = new ExtensionBundleConfigurationHelper(context.Configuration, SystemEnvironment.Instance);
270271
context.Configuration.Bind(options);
271272
optionsSetup.Configure(options);
272273
return options;

0 commit comments

Comments
 (0)