Skip to content

Commit

Permalink
Throw error if regular blob trigger (Azure#9385)
Browse files Browse the repository at this point in the history
  • Loading branch information
alrod authored Jul 17, 2023
1 parent 18dba5b commit 8caea0f
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 12 deletions.
19 changes: 19 additions & 0 deletions src/WebJobs.Script/Extensions/FunctionMetadataExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Azure.WebJobs.Script.Description;
using Newtonsoft.Json.Linq;

namespace Microsoft.Azure.WebJobs.Script
{
Expand All @@ -16,6 +17,9 @@ public static class FunctionMetadataExtensions
private const string FunctionIdKey = "FunctionId";
private const string HttpTriggerKey = "HttpTrigger";
private const string HttpOutputKey = "Http";
private const string BlobTriggerType = "blobTrigger";
private const string BlobSourceKey = "source";
private const string BlobEventGridSourceValue = "EventGrid";

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

public static bool IsLegacyBlobTriggerFunction(this FunctionMetadata metadata)
{
if (metadata.Trigger != null && string.Equals(metadata.Trigger.Type, BlobTriggerType, StringComparison.OrdinalIgnoreCase))
{
if (metadata.Trigger.Raw != null)
{
if (metadata.Trigger.Raw.TryGetValue(BlobSourceKey, StringComparison.OrdinalIgnoreCase, out JToken token) && token != null)
{
return !string.Equals(token.ToString(), BlobEventGridSourceValue, StringComparison.OrdinalIgnoreCase);
}
}
}
return true;
}

public static string GetFunctionId(this FunctionMetadata metadata)
{
if (!metadata.Properties.TryGetValue(FunctionIdKey, out object idObj)
Expand Down
9 changes: 7 additions & 2 deletions src/WebJobs.Script/Host/ScriptHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,7 @@ internal async Task<Collection<FunctionDescriptor>> GetFunctionDescriptorsAsync(

if (descriptor != null)
{
ValidateFunction(descriptor, httpFunctions);
ValidateFunction(descriptor, httpFunctions, _environment);
functionDescriptors.Add(descriptor);
}

Expand All @@ -782,7 +782,7 @@ internal async Task<Collection<FunctionDescriptor>> GetFunctionDescriptorsAsync(
return functionDescriptors;
}

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

httpFunctions.Add(function.Name, httpTrigger);
}
if (environment.IsFlexConsumptionSku()
&& function.Metadata != null && function.Metadata.IsLegacyBlobTriggerFunction())
{
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.");
}
}

internal static void ValidateHttpFunction(string functionName, HttpTriggerAttribute httpTrigger, bool isProxy = false)
Expand Down
70 changes: 60 additions & 10 deletions test/WebJobs.Script.Tests/ScriptHostTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1225,7 +1225,6 @@ public void ValidateFunction_ValidatesHttpRoutes()
{
var httpFunctions = new Dictionary<string, HttpTriggerAttribute>();

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

ScriptHost.ValidateFunction(function.Object, httpFunctions);
ScriptHost.ValidateFunction(function.Object, httpFunctions, _testEnvironment);
Assert.Equal(1, httpFunctions.Count);
Assert.True(httpFunctions.ContainsKey("test"));

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

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

Expand All @@ -1270,7 +1269,7 @@ public void ValidateFunction_ValidatesHttpRoutes()
function.SetupGet(p => p.HttpTriggerAttribute).Returns(() => attribute);
var ex = Assert.Throws<InvalidOperationException>(() =>
{
ScriptHost.ValidateFunction(function.Object, httpFunctions);
ScriptHost.ValidateFunction(function.Object, httpFunctions, _testEnvironment);
});
Assert.Equal("The route specified conflicts with the route defined by function 'test2'.", ex.Message);
Assert.Equal(3, httpFunctions.Count);
Expand All @@ -1284,7 +1283,7 @@ public void ValidateFunction_ValidatesHttpRoutes()
function.SetupGet(p => p.HttpTriggerAttribute).Returns(() => attribute);
ex = Assert.Throws<InvalidOperationException>(() =>
{
ScriptHost.ValidateFunction(function.Object, httpFunctions);
ScriptHost.ValidateFunction(function.Object, httpFunctions, _testEnvironment);
});
Assert.Equal("The specified route conflicts with one or more built in routes.", ex.Message);

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

// verify that empty route is defaulted to function name
function = new Mock<FunctionDescriptor>(MockBehavior.Strict, "test7", null, metadata, null, null, null, null);
attribute = new HttpTriggerAttribute();
function.SetupGet(p => p.HttpTriggerAttribute).Returns(() => attribute);
ScriptHost.ValidateFunction(function.Object, httpFunctions);
ScriptHost.ValidateFunction(function.Object, httpFunctions, _testEnvironment);
Assert.Equal(4, httpFunctions.Count);
Assert.True(httpFunctions.ContainsKey("test7"));
Assert.Equal("test7", attribute.Route);
Expand Down Expand Up @@ -1383,7 +1382,7 @@ public void ValidateFunction_ThrowsOnDuplicateName()
var attribute = new HttpTriggerAttribute(AuthorizationLevel.Function, "get");
function.SetupGet(p => p.HttpTriggerAttribute).Returns(() => attribute);

ScriptHost.ValidateFunction(function.Object, httpFunctions);
ScriptHost.ValidateFunction(function.Object, httpFunctions, _testEnvironment);

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

var ex = Assert.Throws<InvalidOperationException>(() =>
{
ScriptHost.ValidateFunction(function.Object, httpFunctions);
ScriptHost.ValidateFunction(function.Object, httpFunctions, _testEnvironment);
});

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

[Fact]
public void ValidateFunction_ThrowsForLegacyBlobTrigger_OnFlexConsumption()
{
var httpFunctions = new Dictionary<string, HttpTriggerAttribute>();
var name = "test";

var metadata = new FunctionMetadata();
metadata.Bindings.Add(new BindingMetadata()
{
Direction = BindingDirection.In,
Type = "blobTrigger",
Raw = JObject.Parse("{ \"type\": \"blobTrigger\", \"connection\": \"\", \"path\": \"sample1/{name}\", \"name\": \"myBlob\"}")
});
var function = new Mock<FunctionDescriptor>(MockBehavior.Strict, name, null, metadata, null, null, null, null);
function.SetupGet(p => p.HttpTriggerAttribute).Returns(() => null);

TestEnvironment testEnvironment = new TestEnvironment();
testEnvironment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteSku, ScriptConstants.FlexConsumptionSku);

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.";

var ex = Assert.Throws<InvalidOperationException>(() =>
{
ScriptHost.ValidateFunction(function.Object, httpFunctions, testEnvironment);
});
Assert.Equal(errorMessage, ex.Message);

metadata.Bindings.Clear();
metadata.Bindings.Add(new BindingMetadata()
{
Direction = BindingDirection.In,
Type = "blobTrigger",
Raw = JObject.Parse("{ \"type\": \"blobTrigger\", \"connection\": \"\", \"source\": \"LogsAndContainerScan\", \"path\": \"sample1/{name}\", \"name\": \"myBlob\"}")
});
ex = Assert.Throws<InvalidOperationException>(() =>
{
ScriptHost.ValidateFunction(function.Object, httpFunctions, testEnvironment);
});
Assert.Equal(errorMessage, ex.Message);

metadata.Bindings.Clear();
metadata.Bindings.Add(new BindingMetadata()
{
Direction = BindingDirection.In,
Type = "blobTrigger",
Raw = JObject.Parse("{ \"type\": \"blobTrigger\", \"connection\": \"\", \"source\": \"EventGrid\", \"path\": \"sample1/{name}\", \"name\": \"myBlob\"}")
});

ScriptHost.ValidateFunction(function.Object, httpFunctions, testEnvironment);
}

[Fact]
public async Task IsFunction_ReturnsExpectedResult()
{
Expand Down

0 comments on commit 8caea0f

Please sign in to comment.