Skip to content

Commit 8caea0f

Browse files
authored
Throw error if regular blob trigger (#9385)
1 parent 18dba5b commit 8caea0f

File tree

3 files changed

+86
-12
lines changed

3 files changed

+86
-12
lines changed

src/WebJobs.Script/Extensions/FunctionMetadataExtensions.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.Linq;
77
using Microsoft.Azure.WebJobs.Script.Description;
8+
using Newtonsoft.Json.Linq;
89

910
namespace Microsoft.Azure.WebJobs.Script
1011
{
@@ -16,6 +17,9 @@ public static class FunctionMetadataExtensions
1617
private const string FunctionIdKey = "FunctionId";
1718
private const string HttpTriggerKey = "HttpTrigger";
1819
private const string HttpOutputKey = "Http";
20+
private const string BlobTriggerType = "blobTrigger";
21+
private const string BlobSourceKey = "source";
22+
private const string BlobEventGridSourceValue = "EventGrid";
1923

2024
public static bool IsHttpInAndOutFunction(this FunctionMetadata metadata)
2125
{
@@ -40,6 +44,21 @@ public static bool IsHttpTriggerFunction(this FunctionMetadata metadata)
4044
return metadata.InputBindings.Any(b => string.Equals(HttpTriggerKey, b.Type, StringComparison.OrdinalIgnoreCase));
4145
}
4246

47+
public static bool IsLegacyBlobTriggerFunction(this FunctionMetadata metadata)
48+
{
49+
if (metadata.Trigger != null && string.Equals(metadata.Trigger.Type, BlobTriggerType, StringComparison.OrdinalIgnoreCase))
50+
{
51+
if (metadata.Trigger.Raw != null)
52+
{
53+
if (metadata.Trigger.Raw.TryGetValue(BlobSourceKey, StringComparison.OrdinalIgnoreCase, out JToken token) && token != null)
54+
{
55+
return !string.Equals(token.ToString(), BlobEventGridSourceValue, StringComparison.OrdinalIgnoreCase);
56+
}
57+
}
58+
}
59+
return true;
60+
}
61+
4362
public static string GetFunctionId(this FunctionMetadata metadata)
4463
{
4564
if (!metadata.Properties.TryGetValue(FunctionIdKey, out object idObj)

src/WebJobs.Script/Host/ScriptHost.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,7 @@ internal async Task<Collection<FunctionDescriptor>> GetFunctionDescriptorsAsync(
762762

763763
if (descriptor != null)
764764
{
765-
ValidateFunction(descriptor, httpFunctions);
765+
ValidateFunction(descriptor, httpFunctions, _environment);
766766
functionDescriptors.Add(descriptor);
767767
}
768768

@@ -782,7 +782,7 @@ internal async Task<Collection<FunctionDescriptor>> GetFunctionDescriptorsAsync(
782782
return functionDescriptors;
783783
}
784784

785-
internal static void ValidateFunction(FunctionDescriptor function, Dictionary<string, HttpTriggerAttribute> httpFunctions)
785+
internal static void ValidateFunction(FunctionDescriptor function, Dictionary<string, HttpTriggerAttribute> httpFunctions, IEnvironment environment)
786786
{
787787
var httpTrigger = function.HttpTriggerAttribute;
788788
if (httpTrigger != null)
@@ -811,6 +811,11 @@ internal static void ValidateFunction(FunctionDescriptor function, Dictionary<st
811811

812812
httpFunctions.Add(function.Name, httpTrigger);
813813
}
814+
if (environment.IsFlexConsumptionSku()
815+
&& function.Metadata != null && function.Metadata.IsLegacyBlobTriggerFunction())
816+
{
817+
throw new InvalidOperationException($"The Flex Consumption SKU only supports EventGrid as the source for BlobTrigger functions. Please update function '{function.Name}' to use EventGrid. For more information see https://aka.ms/blob-trigger-eg.");
818+
}
814819
}
815820

816821
internal static void ValidateHttpFunction(string functionName, HttpTriggerAttribute httpTrigger, bool isProxy = false)

test/WebJobs.Script.Tests/ScriptHostTests.cs

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,7 +1225,6 @@ public void ValidateFunction_ValidatesHttpRoutes()
12251225
{
12261226
var httpFunctions = new Dictionary<string, HttpTriggerAttribute>();
12271227

1228-
// first add an http function
12291228
var metadata = new FunctionMetadata();
12301229
var function = new Mock<FunctionDescriptor>(MockBehavior.Strict, "test", null, metadata, null, null, null, null);
12311230
var attribute = new HttpTriggerAttribute(AuthorizationLevel.Function, "get")
@@ -1234,7 +1233,7 @@ public void ValidateFunction_ValidatesHttpRoutes()
12341233
};
12351234
function.SetupGet(p => p.HttpTriggerAttribute).Returns(() => attribute);
12361235

1237-
ScriptHost.ValidateFunction(function.Object, httpFunctions);
1236+
ScriptHost.ValidateFunction(function.Object, httpFunctions, _testEnvironment);
12381237
Assert.Equal(1, httpFunctions.Count);
12391238
Assert.True(httpFunctions.ContainsKey("test"));
12401239

@@ -1245,7 +1244,7 @@ public void ValidateFunction_ValidatesHttpRoutes()
12451244
Route = "/foo/bar/baz/"
12461245
};
12471246
function.SetupGet(p => p.HttpTriggerAttribute).Returns(() => attribute);
1248-
ScriptHost.ValidateFunction(function.Object, httpFunctions);
1247+
ScriptHost.ValidateFunction(function.Object, httpFunctions, _testEnvironment);
12491248
Assert.Equal(2, httpFunctions.Count);
12501249
Assert.True(httpFunctions.ContainsKey("test2"));
12511250

@@ -1256,7 +1255,7 @@ public void ValidateFunction_ValidatesHttpRoutes()
12561255
Route = "/foo/bar/baz/"
12571256
};
12581257
function.SetupGet(p => p.HttpTriggerAttribute).Returns(() => attribute);
1259-
ScriptHost.ValidateFunction(function.Object, httpFunctions);
1258+
ScriptHost.ValidateFunction(function.Object, httpFunctions, _testEnvironment);
12601259
Assert.Equal(3, httpFunctions.Count);
12611260
Assert.True(httpFunctions.ContainsKey("test3"));
12621261

@@ -1270,7 +1269,7 @@ public void ValidateFunction_ValidatesHttpRoutes()
12701269
function.SetupGet(p => p.HttpTriggerAttribute).Returns(() => attribute);
12711270
var ex = Assert.Throws<InvalidOperationException>(() =>
12721271
{
1273-
ScriptHost.ValidateFunction(function.Object, httpFunctions);
1272+
ScriptHost.ValidateFunction(function.Object, httpFunctions, _testEnvironment);
12741273
});
12751274
Assert.Equal("The route specified conflicts with the route defined by function 'test2'.", ex.Message);
12761275
Assert.Equal(3, httpFunctions.Count);
@@ -1284,7 +1283,7 @@ public void ValidateFunction_ValidatesHttpRoutes()
12841283
function.SetupGet(p => p.HttpTriggerAttribute).Returns(() => attribute);
12851284
ex = Assert.Throws<InvalidOperationException>(() =>
12861285
{
1287-
ScriptHost.ValidateFunction(function.Object, httpFunctions);
1286+
ScriptHost.ValidateFunction(function.Object, httpFunctions, _testEnvironment);
12881287
});
12891288
Assert.Equal("The specified route conflicts with one or more built in routes.", ex.Message);
12901289

@@ -1297,15 +1296,15 @@ public void ValidateFunction_ValidatesHttpRoutes()
12971296
function.SetupGet(p => p.HttpTriggerAttribute).Returns(() => attribute);
12981297
ex = Assert.Throws<InvalidOperationException>(() =>
12991298
{
1300-
ScriptHost.ValidateFunction(function.Object, httpFunctions);
1299+
ScriptHost.ValidateFunction(function.Object, httpFunctions, _testEnvironment);
13011300
});
13021301
Assert.Equal("The specified route conflicts with one or more built in routes.", ex.Message);
13031302

13041303
// verify that empty route is defaulted to function name
13051304
function = new Mock<FunctionDescriptor>(MockBehavior.Strict, "test7", null, metadata, null, null, null, null);
13061305
attribute = new HttpTriggerAttribute();
13071306
function.SetupGet(p => p.HttpTriggerAttribute).Returns(() => attribute);
1308-
ScriptHost.ValidateFunction(function.Object, httpFunctions);
1307+
ScriptHost.ValidateFunction(function.Object, httpFunctions, _testEnvironment);
13091308
Assert.Equal(4, httpFunctions.Count);
13101309
Assert.True(httpFunctions.ContainsKey("test7"));
13111310
Assert.Equal("test7", attribute.Route);
@@ -1383,7 +1382,7 @@ public void ValidateFunction_ThrowsOnDuplicateName()
13831382
var attribute = new HttpTriggerAttribute(AuthorizationLevel.Function, "get");
13841383
function.SetupGet(p => p.HttpTriggerAttribute).Returns(() => attribute);
13851384

1386-
ScriptHost.ValidateFunction(function.Object, httpFunctions);
1385+
ScriptHost.ValidateFunction(function.Object, httpFunctions, _testEnvironment);
13871386

13881387
// add a proxy with same name
13891388
metadata = new ProxyFunctionMetadata(null);
@@ -1396,12 +1395,63 @@ public void ValidateFunction_ThrowsOnDuplicateName()
13961395

13971396
var ex = Assert.Throws<InvalidOperationException>(() =>
13981397
{
1399-
ScriptHost.ValidateFunction(function.Object, httpFunctions);
1398+
ScriptHost.ValidateFunction(function.Object, httpFunctions, _testEnvironment);
14001399
});
14011400

14021401
Assert.Equal(string.Format($"The function or proxy name '{name}' must be unique within the function app.", name), ex.Message);
14031402
}
14041403

1404+
[Fact]
1405+
public void ValidateFunction_ThrowsForLegacyBlobTrigger_OnFlexConsumption()
1406+
{
1407+
var httpFunctions = new Dictionary<string, HttpTriggerAttribute>();
1408+
var name = "test";
1409+
1410+
var metadata = new FunctionMetadata();
1411+
metadata.Bindings.Add(new BindingMetadata()
1412+
{
1413+
Direction = BindingDirection.In,
1414+
Type = "blobTrigger",
1415+
Raw = JObject.Parse("{ \"type\": \"blobTrigger\", \"connection\": \"\", \"path\": \"sample1/{name}\", \"name\": \"myBlob\"}")
1416+
});
1417+
var function = new Mock<FunctionDescriptor>(MockBehavior.Strict, name, null, metadata, null, null, null, null);
1418+
function.SetupGet(p => p.HttpTriggerAttribute).Returns(() => null);
1419+
1420+
TestEnvironment testEnvironment = new TestEnvironment();
1421+
testEnvironment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteSku, ScriptConstants.FlexConsumptionSku);
1422+
1423+
string errorMessage = "The Flex Consumption SKU only supports EventGrid as the source for BlobTrigger functions. Please update function 'test' to use EventGrid. For more information see https://aka.ms/blob-trigger-eg.";
1424+
1425+
var ex = Assert.Throws<InvalidOperationException>(() =>
1426+
{
1427+
ScriptHost.ValidateFunction(function.Object, httpFunctions, testEnvironment);
1428+
});
1429+
Assert.Equal(errorMessage, ex.Message);
1430+
1431+
metadata.Bindings.Clear();
1432+
metadata.Bindings.Add(new BindingMetadata()
1433+
{
1434+
Direction = BindingDirection.In,
1435+
Type = "blobTrigger",
1436+
Raw = JObject.Parse("{ \"type\": \"blobTrigger\", \"connection\": \"\", \"source\": \"LogsAndContainerScan\", \"path\": \"sample1/{name}\", \"name\": \"myBlob\"}")
1437+
});
1438+
ex = Assert.Throws<InvalidOperationException>(() =>
1439+
{
1440+
ScriptHost.ValidateFunction(function.Object, httpFunctions, testEnvironment);
1441+
});
1442+
Assert.Equal(errorMessage, ex.Message);
1443+
1444+
metadata.Bindings.Clear();
1445+
metadata.Bindings.Add(new BindingMetadata()
1446+
{
1447+
Direction = BindingDirection.In,
1448+
Type = "blobTrigger",
1449+
Raw = JObject.Parse("{ \"type\": \"blobTrigger\", \"connection\": \"\", \"source\": \"EventGrid\", \"path\": \"sample1/{name}\", \"name\": \"myBlob\"}")
1450+
});
1451+
1452+
ScriptHost.ValidateFunction(function.Object, httpFunctions, testEnvironment);
1453+
}
1454+
14051455
[Fact]
14061456
public async Task IsFunction_ReturnsExpectedResult()
14071457
{

0 commit comments

Comments
 (0)