Skip to content

Commit 67907cf

Browse files
authored
Fix functions APIs to return config if no function.json
1 parent 38931a0 commit 67907cf

File tree

3 files changed

+123
-44
lines changed

3 files changed

+123
-44
lines changed

src/WebJobs.Script.WebHost/Extensions/FunctionMetadataExtensions.cs

Lines changed: 63 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ public static class FunctionMetadataExtensions
2222
/// <returns>Promise of a FunctionMetadataResponse</returns>
2323
public static async Task<FunctionMetadataResponse> ToFunctionMetadataResponse(this FunctionMetadata functionMetadata, ScriptJobHostOptions hostOptions, string routePrefix, string baseUrl)
2424
{
25-
var functionPath = Path.Combine(hostOptions.RootScriptPath, functionMetadata.Name);
26-
var functionMetadataFilePath = Path.Combine(functionPath, ScriptConstants.FunctionMetadataFileName);
25+
string functionPath = GetFunctionPathOrNull(hostOptions.RootScriptPath, functionMetadata.Name);
26+
string functionMetadataFilePath = GetMetadataPathOrNull(functionPath);
27+
2728
if (string.IsNullOrEmpty(baseUrl))
2829
{
2930
baseUrl = "https://localhost/";
@@ -32,10 +33,8 @@ public static async Task<FunctionMetadataResponse> ToFunctionMetadataResponse(th
3233
var response = new FunctionMetadataResponse
3334
{
3435
Name = functionMetadata.Name,
35-
ConfigHref = VirtualFileSystem.FilePathToVfsUri(functionMetadataFilePath, baseUrl, hostOptions),
36-
ScriptRootPathHref = VirtualFileSystem.FilePathToVfsUri(functionPath, baseUrl, hostOptions, isDirectory: true),
3736
Href = GetFunctionHref(functionMetadata.Name, baseUrl),
38-
Config = await GetFunctionConfig(functionMetadataFilePath),
37+
Config = await GetFunctionConfig(functionMetadata, functionMetadataFilePath),
3938

4039
// Properties below this comment are not present in the kudu version.
4140
IsDirect = functionMetadata.IsDirect(),
@@ -45,6 +44,16 @@ public static async Task<FunctionMetadataResponse> ToFunctionMetadataResponse(th
4544
InvokeUrlTemplate = GetFunctionInvokeUrlTemplate(baseUrl, functionMetadata, routePrefix)
4645
};
4746

47+
if (!string.IsNullOrEmpty(functionPath))
48+
{
49+
response.ScriptRootPathHref = VirtualFileSystem.FilePathToVfsUri(functionPath, baseUrl, hostOptions, isDirectory: true);
50+
}
51+
52+
if (!string.IsNullOrEmpty(functionMetadataFilePath))
53+
{
54+
response.ConfigHref = VirtualFileSystem.FilePathToVfsUri(functionMetadataFilePath, baseUrl, hostOptions);
55+
}
56+
4857
if (!string.IsNullOrEmpty(hostOptions.TestDataPath))
4958
{
5059
var testDataFilePath = functionMetadata.GetTestDataFilePath(hostOptions);
@@ -69,29 +78,12 @@ public static async Task<FunctionMetadataResponse> ToFunctionMetadataResponse(th
6978
/// <param name="config">ScriptHostConfiguration to read RootScriptPath from.</param>
7079
/// <returns>JObject that represent the trigger for scale controller to consume</returns>
7180
public static async Task<JObject> ToFunctionTrigger(this FunctionMetadata functionMetadata, ScriptJobHostOptions config)
72-
{
73-
// Codeless functions do not have a physical file and need to be converted differently.
74-
if (functionMetadata.IsCodeless())
75-
{
76-
return await GetCodelessFunctionTrigger(functionMetadata);
77-
}
78-
79-
return await GetRegularFunctionTrigger(functionMetadata, config);
80-
}
81-
82-
public static string GetTestDataFilePath(this FunctionMetadata functionMetadata, ScriptJobHostOptions hostOptions) =>
83-
GetTestDataFilePath(functionMetadata.Name, hostOptions);
84-
85-
public static string GetTestDataFilePath(string functionName, ScriptJobHostOptions hostOptions) =>
86-
Path.Combine(hostOptions.TestDataPath, $"{functionName}.dat");
87-
88-
private static async Task<JObject> GetRegularFunctionTrigger(FunctionMetadata functionMetadata, ScriptJobHostOptions config)
8981
{
9082
var functionPath = Path.Combine(config.RootScriptPath, functionMetadata.Name);
9183
var functionMetadataFilePath = Path.Combine(functionPath, ScriptConstants.FunctionMetadataFileName);
9284

9385
// Read function.json as a JObject
94-
var functionConfig = await GetFunctionConfig(functionMetadataFilePath);
86+
var functionConfig = await GetFunctionConfig(functionMetadata, functionMetadataFilePath);
9587

9688
if (functionConfig.TryGetValue("bindings", out JToken value) &&
9789
value is JArray)
@@ -112,35 +104,50 @@ private static async Task<JObject> GetRegularFunctionTrigger(FunctionMetadata fu
112104
return null;
113105
}
114106

115-
private static Task<JObject> GetCodelessFunctionTrigger(FunctionMetadata functionMetadata)
107+
public static string GetTestDataFilePath(this FunctionMetadata functionMetadata, ScriptJobHostOptions hostOptions) =>
108+
GetTestDataFilePath(functionMetadata.Name, hostOptions);
109+
110+
public static string GetTestDataFilePath(string functionName, ScriptJobHostOptions hostOptions) =>
111+
Path.Combine(hostOptions.TestDataPath, $"{functionName}.dat");
112+
113+
private static string GetFunctionPathOrNull(string scriptRoot, string functionName)
116114
{
117-
if (functionMetadata.Bindings == null)
115+
var functionPath = Path.Combine(scriptRoot, functionName);
116+
117+
if (FileUtility.DirectoryExists(functionPath))
118118
{
119-
return null;
119+
return functionPath;
120120
}
121121

122-
foreach (BindingMetadata binding in functionMetadata.Bindings)
122+
return null;
123+
}
124+
125+
private static string GetMetadataPathOrNull(string functionPath)
126+
{
127+
if (!string.IsNullOrEmpty(functionPath))
123128
{
124-
JObject rawBinding = binding.Raw;
125-
var type = (string)rawBinding["type"];
126-
if (type != null && type.EndsWith("Trigger", StringComparison.OrdinalIgnoreCase))
129+
var metadataPath = Path.Combine(functionPath, ScriptConstants.FunctionMetadataFileName);
130+
131+
if (FileUtility.FileExists(metadataPath))
127132
{
128-
JObject newBinding = (JObject)rawBinding.DeepClone();
129-
newBinding.Add("functionName", functionMetadata.Name);
130-
return Task.FromResult(newBinding);
133+
return metadataPath;
131134
}
132135
}
133136

134137
return null;
135138
}
136139

137-
private static async Task<JObject> GetFunctionConfig(string path)
140+
private static async Task<JObject> GetFunctionConfig(FunctionMetadata metadata, string path)
138141
{
139142
try
140143
{
141-
if (FileUtility.FileExists(path))
144+
if (!string.IsNullOrEmpty(path) && FileUtility.FileExists(path))
145+
{
146+
return await GetFunctionConfigFromFile(path);
147+
}
148+
else
142149
{
143-
return JObject.Parse(await FileUtility.ReadAsync(path));
150+
return GetFunctionConfigFromMetadata(metadata);
144151
}
145152
}
146153
catch
@@ -154,6 +161,26 @@ private static async Task<JObject> GetFunctionConfig(string path)
154161
return new JObject();
155162
}
156163

164+
private static async Task<JObject> GetFunctionConfigFromFile(string path)
165+
{
166+
return JObject.Parse(await FileUtility.ReadAsync(path));
167+
}
168+
169+
private static JObject GetFunctionConfigFromMetadata(FunctionMetadata metadata)
170+
{
171+
var config = new
172+
{
173+
name = metadata.Name,
174+
entryPoint = metadata.EntryPoint,
175+
scriptFile = metadata.ScriptFile,
176+
language = metadata.Language,
177+
functionDirectory = metadata.FunctionDirectory,
178+
bindings = metadata.Bindings.Select(m => m.Raw).ToList()
179+
};
180+
181+
return JObject.FromObject(config);
182+
}
183+
157184
private static async Task<string> GetTestData(string testDataPath, ScriptJobHostOptions config)
158185
{
159186
if (!File.Exists(testDataPath))

test/WebJobs.Script.Tests.Integration/Management/FunctionsSyncManagerTests.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,9 @@ private IFileSystem CreateFileSystem(ScriptApplicationHostOptions hostOptions, s
718718
fileSystem.SetupGet(f => f.Directory).Returns(dirBase.Object);
719719
dirBase.Setup(d => d.Exists(rootPath)).Returns(true);
720720
dirBase.Setup(d => d.Exists(Path.Combine(rootPath, "bin"))).Returns(true);
721+
dirBase.Setup(d => d.Exists(Path.Combine(rootPath, @"function1"))).Returns(true);
722+
dirBase.Setup(d => d.Exists(Path.Combine(rootPath, @"function2"))).Returns(true);
723+
dirBase.Setup(d => d.Exists(Path.Combine(rootPath, @"function3"))).Returns(true);
721724
dirBase.Setup(d => d.EnumerateDirectories(rootPath))
722725
.Returns(() =>
723726
{
@@ -781,7 +784,6 @@ private IFileSystem CreateFileSystem(ScriptApplicationHostOptions hostOptions, s
781784
}
782785
]
783786
}";
784-
785787
fileBase.Setup(f => f.Exists(Path.Combine(rootPath, @"function1\function.json"))).Returns(true);
786788
fileBase.Setup(f => f.Exists(Path.Combine(rootPath, @"function1\main.py"))).Returns(true);
787789
fileBase.Setup(f => f.ReadAllText(Path.Combine(rootPath, @"function1\function.json"))).Returns(_function1);

test/WebJobs.Script.Tests/Extensions/FunctionMetadataExtensionsTests.cs

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,28 @@ public async Task ToFunctionTrigger_InvalidConfig_ReturnsNull(string functionCon
6565
Assert.Null(result);
6666
}
6767

68+
[Fact]
69+
public async Task ToFunctionTrigger_NoFile_ReturnsExpected()
70+
{
71+
var functionMetadata = new FunctionMetadata
72+
{
73+
Name = "AnyFunction",
74+
EntryPoint = "MyEntry"
75+
};
76+
77+
AddSampleBindings(functionMetadata);
78+
79+
var options = new ScriptJobHostOptions
80+
{
81+
RootScriptPath = _testRootScriptPath
82+
};
83+
84+
var result = await functionMetadata.ToFunctionTrigger(options);
85+
86+
Assert.Equal("AnyFunction", result["functionName"].Value<string>());
87+
Assert.Equal("httpTrigger", result["type"].Value<string>());
88+
}
89+
6890
[Fact]
6991
public async Task ToFunctionTrigger_Codeless_ReturnsExpected()
7092
{
@@ -79,13 +101,7 @@ public async Task ToFunctionTrigger_Codeless_ReturnsExpected()
79101

80102
functionMetadata.SetIsCodeless(true);
81103

82-
JObject functionConfig = JObject.Parse(_sampleBindingsJson);
83-
JArray bindingArray = (JArray)functionConfig["bindings"];
84-
foreach (JObject binding in bindingArray)
85-
{
86-
BindingMetadata bindingMetadata = BindingMetadata.Create(binding);
87-
functionMetadata.Bindings.Add(bindingMetadata);
88-
}
104+
AddSampleBindings(functionMetadata);
89105

90106
var result = await functionMetadata.ToFunctionTrigger(options);
91107
Assert.Equal("TestFunction1", result["functionName"].Value<string>());
@@ -96,6 +112,29 @@ public async Task ToFunctionTrigger_Codeless_ReturnsExpected()
96112
Assert.Equal("httpTrigger", functionMetadata.Bindings[0].Raw["type"].Value<string>());
97113
}
98114

115+
[Fact]
116+
public async Task ToFunctionMetadataResponse_WithoutFiles_ReturnsExpected()
117+
{
118+
var functionMetadata = new FunctionMetadata
119+
{
120+
Name = "TestFunction1"
121+
};
122+
var options = new ScriptJobHostOptions
123+
{
124+
RootScriptPath = _testRootScriptPath
125+
};
126+
127+
AddSampleBindings(functionMetadata);
128+
var result = await functionMetadata.ToFunctionMetadataResponse(options, string.Empty, null);
129+
130+
Assert.Null(result.ScriptRootPathHref);
131+
Assert.Null(result.ConfigHref);
132+
Assert.Equal("TestFunction1", result.Name);
133+
134+
var binding = result.Config["bindings"] as JArray;
135+
Assert.Equal("httpTrigger", binding[0]["type"].Value<string>());
136+
}
137+
99138
[Fact]
100139
public void GetFunctionInvokeUrlTemplate_ReturnsExpectedResult()
101140
{
@@ -128,5 +167,16 @@ public void GetFunctionInvokeUrlTemplate_ReturnsExpectedResult()
128167
uri = WebHost.Extensions.FunctionMetadataExtensions.GetFunctionInvokeUrlTemplate(baseUrl, functionMetadata, string.Empty);
129168
Assert.Equal("https://localhost/catalog/products/{category:alpha?}/{id:int?}", uri.ToString());
130169
}
170+
171+
private void AddSampleBindings(FunctionMetadata functionMetadata)
172+
{
173+
JObject functionConfig = JObject.Parse(_sampleBindingsJson);
174+
JArray bindingArray = (JArray)functionConfig["bindings"];
175+
foreach (JObject binding in bindingArray)
176+
{
177+
BindingMetadata bindingMetadata = BindingMetadata.Create(binding);
178+
functionMetadata.Bindings.Add(bindingMetadata);
179+
}
180+
}
131181
}
132182
}

0 commit comments

Comments
 (0)