Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/DependencyManagement/DependencyManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ public DependencyManager(
_installer = installer ?? new DependencySnapshotInstaller(
moduleProvider ?? new PowerShellGalleryModuleProvider(),
_storage,
new PowerShellModuleSnapshotComparer());
new PowerShellModuleSnapshotComparer(),
new PowerShellModuleSnapshotLogger());
_newerSnapshotDetector = newerSnapshotDetector ?? new NewerDependencySnapshotDetector(_storage, new WorkerRestarter());
_backgroundSnapshotMaintainer =
maintainer ?? new BackgroundDependencySnapshotMaintainer(
Expand Down
7 changes: 6 additions & 1 deletion src/DependencyManagement/DependencySnapshotInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,18 @@ internal class DependencySnapshotInstaller : IDependencySnapshotInstaller
private readonly IModuleProvider _moduleProvider;
private readonly IDependencyManagerStorage _storage;
private readonly IDependencySnapshotComparer _snapshotComparer;
private readonly IDependencySnapshotContentLogger _snapshotContentLogger;

public DependencySnapshotInstaller(
IModuleProvider moduleProvider,
IDependencyManagerStorage storage,
IDependencySnapshotComparer snapshotComparer)
IDependencySnapshotComparer snapshotComparer,
IDependencySnapshotContentLogger snapshotContentLogger)
{
_moduleProvider = moduleProvider ?? throw new ArgumentNullException(nameof(moduleProvider));
_storage = storage ?? throw new ArgumentNullException(nameof(storage));
_snapshotComparer = snapshotComparer ?? throw new ArgumentNullException(nameof(snapshotComparer));
_snapshotContentLogger = snapshotContentLogger ?? throw new ArgumentNullException(nameof(snapshotContentLogger));
}

public void InstallSnapshot(
Expand Down Expand Up @@ -75,6 +78,8 @@ public void InstallSnapshot(
isUserOnlyLog: false,
LogLevel.Trace,
string.Format(PowerShellWorkerStrings.PromotedDependencySnapshot, installingPath, targetPath));

_snapshotContentLogger.LogDependencySnapshotContent(targetPath, logger);
}
}
catch (Exception e)
Expand Down
14 changes: 14 additions & 0 deletions src/DependencyManagement/IDependencySnapshotContentLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

namespace Microsoft.Azure.Functions.PowerShellWorker.DependencyManagement
{
using Microsoft.Azure.Functions.PowerShellWorker.Utility;

internal interface IDependencySnapshotContentLogger
{
void LogDependencySnapshotContent(string snapshotPath, ILogger logger);
}
}
19 changes: 2 additions & 17 deletions src/DependencyManagement/PowerShellModuleSnapshotComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ public bool AreEquivalent(string snapshotPathA, string snapshotPathB, ILogger lo
{
try
{
var versionSubdirsA = GetModuleVersionSubdirectories(snapshotPathA);
var versionSubdirsB = GetModuleVersionSubdirectories(snapshotPathB);
var versionSubdirsA = PowerShellModuleSnapshotTools.GetModuleVersionSubdirectories(snapshotPathA, _getSubdirectories);
var versionSubdirsB = PowerShellModuleSnapshotTools.GetModuleVersionSubdirectories(snapshotPathB, _getSubdirectories);
return versionSubdirsA.SequenceEqual(versionSubdirsB);
}
catch (IOException e)
Expand All @@ -50,20 +50,5 @@ public bool AreEquivalent(string snapshotPathA, string snapshotPathB, ILogger lo
return false;
}
}

private IEnumerable<string> GetModuleVersionSubdirectories(string snapshotPath)
{
var modulePaths = _getSubdirectories(snapshotPath).ToList();
modulePaths.Sort();
foreach (var modulePath in modulePaths)
{
var versionPaths = _getSubdirectories(modulePath).ToList();
versionPaths.Sort();
foreach (var versionPath in versionPaths)
{
yield return Path.Join(Path.GetFileName(modulePath), Path.GetFileName(versionPath));
}
}
}
}
}
40 changes: 40 additions & 0 deletions src/DependencyManagement/PowerShellModuleSnapshotLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

namespace Microsoft.Azure.Functions.PowerShellWorker.DependencyManagement
{
using System;
using System.IO;
using Microsoft.Azure.Functions.PowerShellWorker.Utility;

using LogLevel = Microsoft.Azure.WebJobs.Script.Grpc.Messages.RpcLog.Types.Level;

internal class PowerShellModuleSnapshotLogger : IDependencySnapshotContentLogger
{
public void LogDependencySnapshotContent(string snapshotPath, ILogger logger)
{
try
{
var moduleVersionSubdirectories = PowerShellModuleSnapshotTools.GetModuleVersionSubdirectories(snapshotPath);

foreach (var moduleVersionSubdirectory in moduleVersionSubdirectories)
{
// Module version subdirectories follow the following pattern: <snapshotPath>\<module name>\<module version>
var moduleName = Path.GetFileName(Path.GetDirectoryName(moduleVersionSubdirectory));
var moduleVersion = Path.GetFileName(moduleVersionSubdirectory);
var snapshotName = Path.GetFileName(snapshotPath);

var message = string.Format(PowerShellWorkerStrings.DependencyShapshotContent, snapshotName, moduleName, moduleVersion);
logger.Log(isUserOnlyLog: false, LogLevel.Trace, message);
}
}
catch (Exception e) when (e is IOException || e is UnauthorizedAccessException)
{
var message = string.Format(PowerShellWorkerStrings.FailedToEnumerateDependencySnapshotContent, snapshotPath, e.Message);
logger.Log(isUserOnlyLog: false, LogLevel.Warning, message);
}
}
}
}
61 changes: 61 additions & 0 deletions src/DependencyManagement/PowerShellModuleSnapshotTools.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

namespace Microsoft.Azure.Functions.PowerShellWorker.DependencyManagement
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

internal class PowerShellModuleSnapshotTools
{
/// <summary>
/// Returns all module version subdirectories, assuming that the directory structure follows
/// regular PowerShell conventions.
///
/// For example, if PowerShell modules are installed into the "C:\Modules" directory
/// and the content of this directory looks like this:
///
/// C:\
/// Modules\
/// ModuleA\
/// 1.0\
/// ...
/// 2.1\
/// ...
/// ModuleB\
/// 1.3.2\
/// ...
/// then GetModuleVersionSubdirectories("C:\Modules") will return the following strings:
///
/// C:\Modules\ModuleA\1.0
/// C:\Modules\ModuleA\2.1
/// C:\Modules\ModuleB\1.3.2
///
/// </summary>
public static IEnumerable<string> GetModuleVersionSubdirectories(string snapshotPath)
{
return GetModuleVersionSubdirectories(snapshotPath, Directory.EnumerateDirectories);
}

public static IEnumerable<string> GetModuleVersionSubdirectories(
string snapshotPath,
Func<string, IEnumerable<string>> getSubdirectories)
{
var modulePaths = getSubdirectories(snapshotPath).ToList();
modulePaths.Sort();
foreach (var modulePath in modulePaths)
{
var versionPaths = getSubdirectories(modulePath).ToList();
versionPaths.Sort();
foreach (var versionPath in versionPaths)
{
yield return Path.Join(Path.GetFileName(modulePath), Path.GetFileName(versionPath));
}
}
}
}
}
6 changes: 6 additions & 0 deletions src/resources/PowerShellWorkerStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -292,4 +292,10 @@
<data name="FunctionInvocationStarting" xml:space="preserve">
<value>Invoking function '{0}' code {1} ms after receiving request. Invocation performance details: {2}</value>
</data>
<data name="DependencyShapshotContent" xml:space="preserve">
<value>Dependency snapshot '{0}' contains module '{1}' version '{2}'.</value>
</data>
<data name="FailedToEnumerateDependencySnapshotContent" xml:space="preserve">
<value>Failed to enumerate dependency snapshot '{0}' content: {1}</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class DependencySnapshotInstallerTests
private readonly Mock<IModuleProvider> _mockModuleProvider = new Mock<IModuleProvider>();
private readonly Mock<IDependencyManagerStorage> _mockStorage = new Mock<IDependencyManagerStorage>(MockBehavior.Strict);
private readonly Mock<IDependencySnapshotComparer> _mockSnapshotComparer = new Mock<IDependencySnapshotComparer>(MockBehavior.Strict);
private readonly Mock<IDependencySnapshotContentLogger> _mockSnapshotContentLogger = new Mock<IDependencySnapshotContentLogger>();
private readonly Mock<ILogger> _mockLogger = new Mock<ILogger>();

private readonly string _targetPathInstalled;
Expand Down Expand Up @@ -94,6 +95,7 @@ public void PromotesInstallingSnapshotToInstalledIfSaveModuleDoesNotThrow()

_mockStorage.Verify(_ => _.CreateInstallingSnapshot(_targetPathInstalled), Times.Once);
_mockStorage.Verify(_ => _.PromoteInstallingSnapshotToInstalledAtomically(_targetPathInstalled), Times.Once);
_mockSnapshotContentLogger.Verify(_ => _.LogDependencySnapshotContent(_targetPathInstalled, _mockLogger.Object), Times.Once);
}

[Fact]
Expand All @@ -110,6 +112,7 @@ public void PromotesInstallingSnapshotToInstalledIfNotEquivalentToLatest()

_mockStorage.Verify(_ => _.CreateInstallingSnapshot(_targetPathInstalled), Times.Once);
_mockStorage.Verify(_ => _.PromoteInstallingSnapshotToInstalledAtomically(_targetPathInstalled), Times.Once);
_mockSnapshotContentLogger.Verify(_ => _.LogDependencySnapshotContent(_targetPathInstalled, _mockLogger.Object), Times.Once);
}

[Fact]
Expand Down Expand Up @@ -230,7 +233,11 @@ public void DoesNotPromoteSnapshotIfItIsEquivalentToLatest()

private DependencySnapshotInstaller CreateDependenciesSnapshotInstallerWithMocks()
{
return new DependencySnapshotInstaller(_mockModuleProvider.Object, _mockStorage.Object, _mockSnapshotComparer.Object);
return new DependencySnapshotInstaller(
_mockModuleProvider.Object,
_mockStorage.Object,
_mockSnapshotComparer.Object,
_mockSnapshotContentLogger.Object);
}

private void VerifyLoggedOnce(IEnumerable<string> messageParts)
Expand Down