Skip to content

Commit

Permalink
Fixes upgrade dependency could use pre-release microsoft#2726
Browse files Browse the repository at this point in the history
  • Loading branch information
BernieWhite committed Jan 21, 2025
1 parent 6d9b60c commit 5cdf5a5
Show file tree
Hide file tree
Showing 14 changed files with 96 additions and 21 deletions.
6 changes: 6 additions & 0 deletions docs/CHANGELOG-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers

## Unreleased

What's changed since pre-release v3.0.0-B0390:

- Bug fixes:
- Fixed upgrade dependency could use pre-release version by @BernieWhite.
[#2726](https://github.com/microsoft/PSRule/issues/2726)

## v3.0.0-B0390 (pre-release)

What's changed since pre-release v3.0.0-B0351:
Expand Down
54 changes: 46 additions & 8 deletions src/PSRule.CommandLine/Commands/ModuleCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,9 +277,10 @@ public static async Task<int> ModuleAddAsync(ModuleOptions operationOptions, Cli
return ERROR_MODULE_FAILED_TO_FIND;
}

if (!IsInstalled(pwsh, module, idealVersion, out _, out _))
if (!IsInstalled(pwsh, module, idealVersion, out _, out _) && await InstallVersionAsync(clientContext, module, idealVersion, null, cancellationToken) == null)
{
await InstallVersionAsync(clientContext, module, idealVersion, null, cancellationToken);
clientContext.LogError(Messages.Error_501, module, idealVersion);
return ERROR_MODULE_FAILED_TO_INSTALL;
}

clientContext.LogVerbose(Messages.UsingModule, module, idealVersion.ToString());
Expand Down Expand Up @@ -352,8 +353,17 @@ public static async Task<int> ModuleUpgradeAsync(ModuleOptions operationOptions,
using var pwsh = CreatePowerShell();
foreach (var kv in file.Modules.Where(m => filteredModules == null || filteredModules.Contains(m.Key)))
{
// Get a constraint if set from options.
var moduleConstraint = requires.TryGetValue(kv.Key, out var c) ? c : ModuleConstraint.Any(kv.Key, includePrerelease: kv.Value.IncludePrerelease ?? operationOptions.Prerelease);
var includePrerelease = kv.Value.IncludePrerelease ?? operationOptions.Prerelease;

// Get the module constraint.
var moduleConstraint = ModuleConstraint.Any(kv.Key, includePrerelease: includePrerelease);

// Use the constraint set in options.
if (requires.TryGetValue(kv.Key, out var c))
{
// Only allow pre-releases if both the constraint and lock file/ context allows it.
moduleConstraint = !includePrerelease ? c.Stable() : c;
}

// Find the ideal version.
var idealVersion = await FindVersionAsync(kv.Key, moduleConstraint, null, null, cancellationToken);
Expand All @@ -366,9 +376,10 @@ public static async Task<int> ModuleUpgradeAsync(ModuleOptions operationOptions,
if (idealVersion == kv.Value.Version)
continue;

if (!IsInstalled(pwsh, kv.Key, idealVersion, out _, out _))
if (!IsInstalled(pwsh, kv.Key, idealVersion, out _, out _) && await InstallVersionAsync(clientContext, kv.Key, idealVersion, null, cancellationToken) == null)
{
await InstallVersionAsync(clientContext, kv.Key, idealVersion, null, cancellationToken);
clientContext.LogError(Messages.Error_501, kv.Key, idealVersion);
return ERROR_MODULE_FAILED_TO_INSTALL;
}

clientContext.LogVerbose(Messages.UsingModule, kv.Key, idealVersion.ToString());
Expand Down Expand Up @@ -580,7 +591,7 @@ private static bool TryPrivateData(PSModuleInfo info, string propertyName, out H
// Clean up the temp path.
if (Directory.Exists(tempPath))
{
Directory.Delete(tempPath, true);
Retry(3, 1000, () => Directory.Delete(tempPath, true));
}

context.LogError(Messages.Error_504, name, version);
Expand All @@ -593,7 +604,7 @@ private static bool TryPrivateData(PSModuleInfo info, string propertyName, out H
Directory.CreateDirectory(parentDirectory);

// Move the module to the final path.
Directory.Move(tempPath, modulePath);
Retry(3, 1000, () => Directory.Move(tempPath, modulePath));

if (!Directory.Exists(modulePath))
return null;
Expand Down Expand Up @@ -632,5 +643,32 @@ private static PowerShell CreatePowerShell()
return PowerShell.Create();
}

/// <summary>
/// Retry an action a number of times with a delay.
/// </summary>
/// <param name="retryCount">The number of retries.</param>
/// <param name="delay">The delay in milliseconds.</param>
/// <param name="action">The action to attempt.</param>
private static void Retry(int retryCount, int delay, Action action)
{
var attempts = 0;
while (attempts < retryCount)
{
try
{
action();
return;
}
catch (Exception)
{
attempts++;
if (attempts >= retryCount)
throw;

Thread.Sleep(delay);
}
}
}

#endregion Helper methods
}
2 changes: 1 addition & 1 deletion src/PSRule.CommandLine/Resources/Messages.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/PSRule.CommandLine/Resources/Messages.resx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Error_501" xml:space="preserve">
<value>Failed to restore module: {0} -- v{1}</value>
<value>Failed to install module: {0} -- v{1}</value>
</data>
<data name="Error_502" xml:space="preserve">
<value>Failed to find a valid version of the specified module '{0}'.</value>
Expand Down
16 changes: 15 additions & 1 deletion src/PSRule.Types/Data/ModuleConstraint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ namespace PSRule.Data;
[DebuggerDisplay("{Module}")]
public sealed class ModuleConstraint(string module, ISemanticVersionConstraint constraint) : ISemanticVersionConstraint
{
private bool _RequireStableVersions = false;

/// <summary>
/// The name of the module.
/// </summary>
Expand All @@ -25,7 +27,19 @@ public sealed class ModuleConstraint(string module, ISemanticVersionConstraint c
public ISemanticVersionConstraint Constraint { get; } = constraint ?? throw new ArgumentNullException(nameof(constraint));

/// <inheritdoc/>
public bool Accepts(SemanticVersion.Version? version) => Constraint.Accepts(version);
public bool Accepts(SemanticVersion.Version? version)
{
return version != null && _RequireStableVersions && !version.Stable ? false : Constraint.Accepts(version);
}

/// <summary>
/// Flag that any accept versions must be stable.
/// </summary>
public ModuleConstraint Stable()
{
_RequireStableVersions = true;
return this;
}

/// <summary>
/// Get a constraint that accepts any version of the specified module.
Expand Down
2 changes: 1 addition & 1 deletion src/PSRule.Types/Data/SemanticVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ private string GetExpressionString()
/// <summary>
/// A semantic version.
/// </summary>
[DebuggerDisplay("{_VersionString}")]
[DebuggerDisplay("{ToString()}")]
public sealed class Version : IComparable<Version>, IEquatable<Version>
{
private string? _VersionString;
Expand Down
3 changes: 1 addition & 2 deletions src/PSRule/Pipeline/Dependencies/IntegrityBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ private sealed class FileIntegrity(string path, string hash)
/// <param name="path">The directory path to the dependency.</param>
public static LockEntryIntegrity Build(IntegrityAlgorithm alg, string path)
{
if (!Directory.Exists(path))
throw new InvalidOperationException($"The path '{path}' does not exist.");
if (!Directory.Exists(path)) throw new InvalidOperationException($"The path '{path}' does not exist.");

var ignoredFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
Expand Down
2 changes: 2 additions & 0 deletions src/PSRule/Pipeline/Dependencies/LockEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using Newtonsoft.Json;
using PSRule.Converters.Json;
using PSRule.Data;
Expand All @@ -14,6 +15,7 @@ namespace PSRule.Pipeline.Dependencies;
/// <summary>
/// An entry within the lock file.
/// </summary>
[DebuggerDisplay("{Version.ToString()}, IncludePrerelease={IncludePrerelease}, {Integrity.Hash}")]
public sealed class LockEntry(SemanticVersion.Version version)
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\PSRule.LangServer\PSRule.LangServer.csproj" />
<ProjectReference Include="..\..\src\PSRule.EditorServices\PSRule.EditorServices.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
10 changes: 10 additions & 0 deletions tests/PSRule.Tests/Pipeline/Dependencies/LockFileTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,20 @@ public void Read_WhenValidLockFile_ShouldReturnInstance()
Assert.Equal("1.1.0", item.Version.ToString());
Assert.Equal(IntegrityAlgorithm.SHA512, item.Integrity.Algorithm);
Assert.Equal("4oEbkAT3VIQQlrDUOpB9qKkbNU5BMktvkDCriws4LgCMUiyUoYMcN0XovljAIW4FO0cmP7mP6A8Z7MPNGlgK7Q==", item.Integrity.Hash);
Assert.Null(item.IncludePrerelease);

// Test string casing.
Assert.True(lockFile.Modules.TryGetValue("psrule.rules.msft.oss", out item));
Assert.Equal("1.1.0", item.Version.ToString());
Assert.Equal(IntegrityAlgorithm.SHA512, item.Integrity.Algorithm);
Assert.Equal("4oEbkAT3VIQQlrDUOpB9qKkbNU5BMktvkDCriws4LgCMUiyUoYMcN0XovljAIW4FO0cmP7mP6A8Z7MPNGlgK7Q==", item.Integrity.Hash);
Assert.Null(item.IncludePrerelease);

// Test second module.
Assert.True(lockFile.Modules.TryGetValue("PSRule.Rules.Azure", out item));
Assert.Equal("1.39.3", item.Version.ToString());
Assert.Equal(IntegrityAlgorithm.SHA512, item.Integrity.Algorithm);
Assert.Equal("BS6NhS0xlt7+iLoBWchc72I3/LAi1bWum9jV48aKNfQ/02lzrCiSgUHu3Svc0sS3oICdSfO3zoxlcI24Oo3Zfw==", item.Integrity.Hash);
Assert.True(item.IncludePrerelease);
}
}
5 changes: 5 additions & 0 deletions tests/PSRule.Tests/test.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
"PSRule.Rules.MSFT.OSS": {
"version": "1.1.0",
"integrity": "sha512-4oEbkAT3VIQQlrDUOpB9qKkbNU5BMktvkDCriws4LgCMUiyUoYMcN0XovljAIW4FO0cmP7mP6A8Z7MPNGlgK7Q=="
},
"PSRule.Rules.Azure": {
"version": "1.39.3",
"includePrerelease": true,
"integrity": "sha512-BS6NhS0xlt7+iLoBWchc72I3/LAi1bWum9jV48aKNfQ/02lzrCiSgUHu3Svc0sS3oICdSfO3zoxlcI24Oo3Zfw=="
}
}
}
2 changes: 0 additions & 2 deletions tests/PSRule.Tool.Tests/CommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@

using System.CommandLine;
using System.CommandLine.IO;
using System.Linq;
using System.Threading.Tasks;

namespace PSRule.Tool;

Expand Down
9 changes: 6 additions & 3 deletions tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ProjectGuid>{c1d2bc26-305a-4985-8dc5-177449ff2cfd}</ProjectGuid>
<IsTestProject>true</IsTestProject>
<IsPackable>false</IsPackable>
<RootNamespace>PSRule.Tool</RootNamespace>
<ProjectGuid>{c1d2bc26-305a-4985-8dc5-177449ff2cfd}</ProjectGuid>
<DebugType>Full</DebugType>
<LangVersion>12.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion tests/PSRule.Types.Tests/PSRule.Types.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>PSRule</RootNamespace>
<ProjectGuid>{8860178f-4b4a-4e28-8cc3-85dfe2a2fe4b}</ProjectGuid>
<DebugType>Full</DebugType>
<LangVersion>12.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
Expand Down

0 comments on commit 5cdf5a5

Please sign in to comment.