Skip to content

Commit 312c012

Browse files
committed
Add support for file event subscribers
1 parent e8fc393 commit 312c012

File tree

7 files changed

+82
-5
lines changed

7 files changed

+82
-5
lines changed

release_notes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@
33
- My change description (#PR)
44
-->
55

6+
- Add a new host.json property "watchFiles" for restarting the Host when files are modified.
7+
68
**Release sprint:** Sprint 83
79
[ [bugs](https://github.com/Azure/azure-functions-host/issues?q=is%3Aissue+milestone%3A%22Functions+Sprint+83%22+label%3Abug+is%3Aclosed) | [features](https://github.com/Azure/azure-functions-host/issues?q=is%3Aissue+milestone%3A%22Functions+Sprint+83%22+label%3Afeature+is%3Aclosed) ]

src/WebJobs.Script.WebHost/FileMonitoringService.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,9 +248,7 @@ private void OnFileChanged(FileSystemEventArgs e)
248248
Shutdown();
249249
}
250250
}
251-
else if (string.Compare(fileName, ScriptConstants.HostMetadataFileName, StringComparison.OrdinalIgnoreCase) == 0 ||
252-
string.Compare(fileName, ScriptConstants.FunctionMetadataFileName, StringComparison.OrdinalIgnoreCase) == 0 ||
253-
string.Compare(fileName, ScriptConstants.ProxyMetadataFileName, StringComparison.OrdinalIgnoreCase) == 0)
251+
else if (_scriptOptions.WatchFiles.Any(f => string.Equals(fileName, f, StringComparison.OrdinalIgnoreCase)))
254252
{
255253
changeDescription = "File";
256254
}

src/WebJobs.Script/Config/HostJsonFileConfigurationSource.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public class HostJsonFileConfigurationProvider : ConfigurationProvider
4949
{
5050
private static readonly string[] WellKnownHostJsonProperties = new[]
5151
{
52-
"version", "functionTimeout", "functions", "http", "watchDirectories", "queues", "serviceBus",
52+
"version", "functionTimeout", "functions", "http", "watchDirectories", "watchFiles", "queues", "serviceBus",
5353
"eventHub", "singleton", "logging", "aggregator", "healthMonitor", "extensionBundle", "managedDependencies",
5454
"customHandler", "httpWorker"
5555
};

src/WebJobs.Script/Config/ScriptHostOptionsSetup.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ public void Configure(ScriptJobHostOptions options)
3030
// Add the standard built in watched directories set to any the user may have specified
3131
options.WatchDirectories.Add("node_modules");
3232

33+
// Add the default files we need to watch
34+
options.WatchFiles.Add(ScriptConstants.HostMetadataFileName);
35+
options.WatchFiles.Add(ScriptConstants.FunctionMetadataFileName);
36+
options.WatchFiles.Add(ScriptConstants.ProxyMetadataFileName);
37+
3338
// Set default logging mode
3439
options.FileLoggingMode = FileLoggingMode.DebugOnly;
3540

src/WebJobs.Script/Config/ScriptJobHostOptions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public ScriptJobHostOptions()
1919
FileLoggingMode = FileLoggingMode.Never;
2020
InstanceId = Guid.NewGuid().ToString();
2121
WatchDirectories = new Collection<string>();
22+
WatchFiles = new Collection<string>();
2223
}
2324

2425
/// <summary>
@@ -70,6 +71,14 @@ public string RootScriptPath
7071
/// </summary>
7172
public ICollection<string> WatchDirectories { get; set; }
7273

74+
/// <summary>
75+
/// Gets or sets the collection of file names that
76+
/// should be monitored for changes. If FileWatchingEnabled is true, these files
77+
/// will be monitored. When a file from this list is added/modified/deleted,
78+
/// the host will restart.
79+
/// </summary>
80+
public ICollection<string> WatchFiles { get; set; }
81+
7382
/// <summary>
7483
/// Gets or sets a value governing when logs should be written to disk.
7584
/// When enabled, logs will be written to the directory specified by

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ public void Configure_FileWatching()
3535
Assert.Equal(1, options.WatchDirectories.Count);
3636
Assert.Equal("node_modules", options.WatchDirectories.ElementAt(0));
3737

38+
Assert.Equal(3, options.WatchFiles.Count);
39+
Assert.Contains("host.json", options.WatchFiles);
40+
Assert.Contains("function.json", options.WatchFiles);
41+
Assert.Contains("proxies.json", options.WatchFiles);
42+
3843
// File watching disabled
3944
settings[ConfigurationPath.Combine(ConfigurationSectionNames.JobHost, "fileWatchingEnabled")] = bool.FalseString;
4045

@@ -49,6 +54,8 @@ public void Configure_FileWatching()
4954
settings[ConfigurationPath.Combine(ConfigurationSectionNames.JobHost, "fileWatchingEnabled")] = bool.TrueString;
5055
settings[ConfigurationPath.Combine(ConfigurationSectionNames.JobHost, "watchDirectories", "0")] = "Shared";
5156
settings[ConfigurationPath.Combine(ConfigurationSectionNames.JobHost, "watchDirectories", "1")] = "Tools";
57+
settings[ConfigurationPath.Combine(ConfigurationSectionNames.JobHost, "watchFiles", "0")] = "myFirstFile.ext";
58+
settings[ConfigurationPath.Combine(ConfigurationSectionNames.JobHost, "watchFiles", "1")] = "mySecondFile.ext";
5259

5360
setup = CreateSetupWithConfiguration(settings);
5461

@@ -60,6 +67,13 @@ public void Configure_FileWatching()
6067
Assert.Equal("node_modules", options.WatchDirectories.ElementAt(0));
6168
Assert.Equal("Shared", options.WatchDirectories.ElementAt(1));
6269
Assert.Equal("Tools", options.WatchDirectories.ElementAt(2));
70+
71+
Assert.Equal(5, options.WatchFiles.Count);
72+
Assert.Contains("host.json", options.WatchFiles);
73+
Assert.Contains("function.json", options.WatchFiles);
74+
Assert.Contains("proxies.json", options.WatchFiles);
75+
Assert.Contains("myFirstFile.ext", options.WatchFiles);
76+
Assert.Contains("mySecondFile.ext", options.WatchFiles);
6377
}
6478

6579
[Fact(Skip = "ApplyConfiguration no longer exists. Validate logic (moved to HostJsonFileConfigurationSource)")]

test/WebJobs.Script.Tests/FileMonitoringServiceTests.cs

Lines changed: 50 additions & 1 deletion
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.IO;
56
using System.Threading;
67
using System.Threading.Tasks;
@@ -56,6 +57,53 @@ public void InitializesGetDirectorySnapshot()
5657
}
5758
}
5859

60+
[Fact]
61+
public static async Task HostRestarts_OnWatchFilesChange()
62+
{
63+
using (var directory = new TempDirectory())
64+
{
65+
// Setup
66+
string tempDir = directory.Path;
67+
Directory.CreateDirectory(Path.Combine(tempDir, "Host"));
68+
File.Create(Path.Combine(tempDir, "my_watched_file.txt"));
69+
File.Create(Path.Combine(tempDir, "my_ignored_file.txt"));
70+
71+
var jobHostOptions = new ScriptJobHostOptions
72+
{
73+
RootLogPath = tempDir,
74+
RootScriptPath = tempDir,
75+
FileWatchingEnabled = true,
76+
WatchFiles = { "my_watched_file.txt" }
77+
};
78+
79+
var loggerFactory = new LoggerFactory();
80+
var mockApplicationLifetime = new Mock<IApplicationLifetime>();
81+
var mockScriptHostManager = new Mock<IScriptHostManager>();
82+
var mockEventManager = new ScriptEventManager();
83+
var environment = new TestEnvironment();
84+
85+
// Act
86+
FileMonitoringService fileMonitoringService = new FileMonitoringService(new OptionsWrapper<ScriptJobHostOptions>(jobHostOptions),
87+
loggerFactory, mockEventManager, mockApplicationLifetime.Object, mockScriptHostManager.Object, environment);
88+
await fileMonitoringService.StartAsync(new CancellationToken(canceled: false));
89+
90+
var ignoredFileEventArgs = new FileSystemEventArgs(WatcherChangeTypes.Created, tempDir, "my_ignored_file.txt");
91+
FileEvent ignoredFileEvent = new FileEvent("ScriptFiles", ignoredFileEventArgs);
92+
93+
var watchedFileEventArgs = new FileSystemEventArgs(WatcherChangeTypes.Created, tempDir, "my_watched_file.txt");
94+
FileEvent watchedFileEvent = new FileEvent("ScriptFiles", watchedFileEventArgs);
95+
96+
// Test
97+
mockEventManager.Publish(ignoredFileEvent);
98+
await Task.Delay(TimeSpan.FromSeconds(3));
99+
mockScriptHostManager.Verify(m => m.RestartHostAsync(default), Times.Never);
100+
101+
mockEventManager.Publish(watchedFileEvent);
102+
await Task.Delay(TimeSpan.FromSeconds(3));
103+
mockScriptHostManager.Verify(m => m.RestartHostAsync(default));
104+
}
105+
}
106+
59107
[Theory]
60108
[InlineData("app_offline.htm", 150, true, false)]
61109
[InlineData("app_offline.htm", 10, true, false)]
@@ -75,7 +123,8 @@ public static async Task TestAppOfflineDebounceTime(string fileName, int delayIn
75123
{
76124
RootLogPath = tempDir,
77125
RootScriptPath = tempDir,
78-
FileWatchingEnabled = true
126+
FileWatchingEnabled = true,
127+
WatchFiles = { "host.json" }
79128
};
80129
var loggerFactory = new LoggerFactory();
81130
var mockApplicationLifetime = new Mock<IApplicationLifetime>();

0 commit comments

Comments
 (0)