Skip to content

Commit ecb23de

Browse files
authored
Fix ExternalConfigurationStartupValidator bug (#7667) (#7685)
1 parent 8596a27 commit ecb23de

File tree

8 files changed

+163
-13
lines changed

8 files changed

+163
-13
lines changed

src/WebJobs.Script/DependencyInjection/ExternalConfigurationStartupValidator.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ public IDictionary<string, IEnumerable<string>> Validate(IConfigurationRoot orig
4343

4444
foreach (var function in functions)
4545
{
46-
var trigger = function.Bindings.SingleOrDefault(b => b.IsTrigger);
47-
46+
// Only a single trigger per function is supported. For our purposes here we just take
47+
// the first. If multiple are defined, that error will be handled on indexing.
48+
var trigger = function.Bindings.FirstOrDefault(b => b.IsTrigger);
4849
if (trigger == null)
4950
{
5051
continue;

src/WebJobs.Script/Description/FunctionDescriptorProvider.cs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,6 @@ private bool TryParseTriggerParameter(JObject metadata, out ParameterDescriptor
178178

179179
protected internal virtual void ValidateFunction(FunctionMetadata functionMetadata)
180180
{
181-
// Functions must have a trigger binding
182-
var triggerMetadata = functionMetadata.InputBindings.FirstOrDefault(p => p.IsTrigger);
183-
if (triggerMetadata == null)
184-
{
185-
throw new InvalidOperationException("No trigger binding specified. A function must have a trigger input binding.");
186-
}
187-
188181
HashSet<string> names = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
189182
foreach (var binding in functionMetadata.Bindings)
190183
{
@@ -200,6 +193,20 @@ protected internal virtual void ValidateFunction(FunctionMetadata functionMetada
200193
names.Add(binding.Name);
201194
}
202195
}
196+
197+
// Verify there aren't multiple triggers defined
198+
var triggers = functionMetadata.InputBindings.Where(p => p.IsTrigger).ToArray();
199+
if (triggers.Length > 1)
200+
{
201+
throw new InvalidOperationException($"Multiple trigger bindings defined. A function can only have a single trigger binding.");
202+
}
203+
204+
// Functions must have a trigger binding
205+
var triggerMetadata = triggers.FirstOrDefault(p => p.IsTrigger);
206+
if (triggerMetadata == null)
207+
{
208+
throw new InvalidOperationException("No trigger binding specified. A function must have a trigger input binding.");
209+
}
203210
}
204211

205212
protected internal virtual void ValidateBinding(BindingMetadata bindingMetadata)
@@ -213,6 +220,11 @@ protected internal virtual void ValidateBinding(BindingMetadata bindingMetadata)
213220
{
214221
throw new ArgumentException($"{ScriptConstants.SystemReturnParameterBindingName} bindings must specify a direction of 'out'.");
215222
}
223+
224+
if (bindingMetadata.Type == null)
225+
{
226+
throw new ArgumentException($"Binding '{bindingMetadata.Name}' is invalid. Bindings must specify a Type.");
227+
}
216228
}
217229

218230
protected static void ApplyMethodLevelAttributes(FunctionMetadata functionMetadata, BindingMetadata triggerMetadata, Collection<CustomAttributeBuilder> methodAttributes)

test/WebJobs.Script.Tests.Integration/TestFunctionHost.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,13 @@ public TestFunctionHost(string scriptPath, string logPath,
167167
// store off a bit of the creation stack for easier debugging if this host doesn't shut down.
168168
var stack = new StackTrace(true).ToString().Split(Environment.NewLine).Take(5);
169169
_createdStack = string.Join($"{Environment.NewLine} ", stack);
170+
171+
// cache startup logs since tests clear logs from time to time
172+
StartupLogs = GetScriptHostLogMessages();
170173
}
171174

175+
public IList<LogMessage> StartupLogs { get; }
176+
172177
public IServiceProvider JobHostServices => _hostService.Services;
173178

174179
public IServiceProvider WebHostServices => _testServer.Host.Services;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"bindings": [
3+
{
4+
"type": "httpTrigger",
5+
"name": "req",
6+
"direction": "in",
7+
"methods": [ "get" ],
8+
"authLevel": "anonymous"
9+
},
10+
{
11+
"type": "httpTrigger",
12+
"name": "req2",
13+
"direction": "in",
14+
"methods": [ "get" ],
15+
"authLevel": "anonymous"
16+
},
17+
{
18+
"type": "http",
19+
"name": "res",
20+
"direction": "out"
21+
}
22+
]
23+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using System.Net;
2+
using Microsoft.AspNetCore.Mvc;
3+
using Microsoft.Extensions.Primitives;
4+
5+
public static IActionResult Run(HttpRequest req)
6+
{
7+
return new OkResult();
8+
}

test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/CSharpEndToEndTests.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using Microsoft.CodeAnalysis.CSharp;
2121
using Microsoft.Extensions.DependencyInjection;
2222
using Microsoft.Extensions.Hosting;
23+
using Microsoft.Extensions.Logging;
2324
using Microsoft.WebJobs.Script.Tests;
2425
using Newtonsoft.Json.Linq;
2526
using Xunit;
@@ -276,6 +277,14 @@ public async Task MissingAssemblies_ShowsHelpfulMessage()
276277
Assert.Equal(true, hasWarning);
277278
}
278279

280+
[Fact]
281+
public void MultipleTriggers_ShowsHelpfulMessage()
282+
{
283+
var logs = Fixture.Host.StartupLogs.Where(p => p.Level == LogLevel.Error && p.FormattedMessage.Contains("MultipleTriggers")).Select(p => p.FormattedMessage).Where(p => p != null).ToArray();
284+
var log = logs.Single();
285+
Assert.Equal("The 'MultipleTriggers' function is in error: Multiple trigger bindings defined. A function can only have a single trigger binding.", log);
286+
}
287+
279288
[Fact]
280289
public async Task ExecutionContext_IsPopulated()
281290
{
@@ -578,7 +587,8 @@ public override void ConfigureScriptHost(IWebJobsBuilder webJobsBuilder)
578587
"QueueTriggerToBlob",
579588
"Scenarios",
580589
"FunctionIndexingError",
581-
"MissingAssemblies"
590+
"MissingAssemblies",
591+
"MultipleTriggers"
582592
};
583593
});
584594

test/WebJobs.Script.Tests/Configuration/ExternalConfigurationStartupValidatorTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4+
using System;
45
using System.Collections.Generic;
56
using System.Collections.ObjectModel;
67
using System.Linq;
@@ -76,6 +77,48 @@ public ExternalConfigurationStartupValidatorTests()
7677
});
7778
}
7879

80+
[Fact]
81+
public void MultipleTriggers_Succeeds()
82+
{
83+
BindingMetadata bindingMetadata1 = new BindingMetadata
84+
{
85+
Type = "ATrigger",
86+
Raw = JObject.FromObject(new
87+
{
88+
key0 = "foo"
89+
})
90+
};
91+
92+
BindingMetadata bindingMetadata2 = new BindingMetadata
93+
{
94+
Type = "BTrigger",
95+
Raw = JObject.FromObject(new
96+
{
97+
key0 = "bar"
98+
})
99+
};
100+
101+
FunctionMetadata functionMetadata = new FunctionMetadata
102+
{
103+
Name = "test"
104+
};
105+
106+
functionMetadata.Bindings.Add(bindingMetadata1);
107+
functionMetadata.Bindings.Add(bindingMetadata2);
108+
109+
ICollection<FunctionMetadata> metadata = new Collection<FunctionMetadata>
110+
{
111+
functionMetadata
112+
};
113+
114+
var metadataManager = new MockMetadataManager(metadata);
115+
var config = _configBuilder.Build();
116+
117+
var validator = new ExternalConfigurationStartupValidator(config, metadataManager);
118+
var invalidValues = validator.Validate(config);
119+
Assert.Empty(invalidValues);
120+
}
121+
79122
[Fact]
80123
public void ChangedLookup_ReturnsInvalidValues()
81124
{

test/WebJobs.Script.Tests/Description/FunctionDescriptorProviderTests.cs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,13 @@ public void ValidateFunction_DuplicateBindingNames_Throws()
5656
});
5757
functionMetadata.Bindings.Add(new BindingMetadata
5858
{
59-
Name = "dupe"
59+
Name = "dupe",
60+
Type = "Blob"
6061
});
6162
functionMetadata.Bindings.Add(new BindingMetadata
6263
{
63-
Name = "dupe"
64+
Name = "dupe",
65+
Type = "Blob"
6466
});
6567

6668
var ex = Assert.Throws<InvalidOperationException>(() =>
@@ -71,6 +73,51 @@ public void ValidateFunction_DuplicateBindingNames_Throws()
7173
Assert.Equal("Multiple bindings with name 'dupe' discovered. Binding names must be unique.", ex.Message);
7274
}
7375

76+
[Fact]
77+
public void ValidateFunction_NoBindingType_Throws()
78+
{
79+
FunctionMetadata functionMetadata = new FunctionMetadata();
80+
functionMetadata.Bindings.Add(new BindingMetadata
81+
{
82+
Name = "a",
83+
Type = "BlobTrigger"
84+
});
85+
functionMetadata.Bindings.Add(new BindingMetadata
86+
{
87+
Name = "b"
88+
});
89+
90+
var ex = Assert.Throws<ArgumentException>(() =>
91+
{
92+
_provider.ValidateFunction(functionMetadata);
93+
});
94+
95+
Assert.Equal("Binding 'b' is invalid. Bindings must specify a Type.", ex.Message);
96+
}
97+
98+
[Fact]
99+
public void ValidateFunction_MultipleTriggerBindings_Throws()
100+
{
101+
FunctionMetadata functionMetadata = new FunctionMetadata();
102+
functionMetadata.Bindings.Add(new BindingMetadata
103+
{
104+
Name = "a",
105+
Type = "BlobTrigger"
106+
});
107+
functionMetadata.Bindings.Add(new BindingMetadata
108+
{
109+
Name = "b",
110+
Type = "QueueTrigger"
111+
});
112+
113+
var ex = Assert.Throws<InvalidOperationException>(() =>
114+
{
115+
_provider.ValidateFunction(functionMetadata);
116+
});
117+
118+
Assert.Equal("Multiple trigger bindings defined. A function can only have a single trigger binding.", ex.Message);
119+
}
120+
74121
[Fact]
75122
public void ValidateFunction_NoTriggerBinding_Throws()
76123
{
@@ -173,7 +220,8 @@ public void ValidateBinding_ValidName_DoesNotThrow(string bindingName)
173220
{
174221
BindingMetadata bindingMetadata = new BindingMetadata
175222
{
176-
Name = bindingName
223+
Name = bindingName,
224+
Type = "Blob"
177225
};
178226

179227
if (bindingMetadata.IsReturn)

0 commit comments

Comments
 (0)