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
4 changes: 4 additions & 0 deletions docs/CHANGELOG-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers

## Unreleased

- General improvements:
- Automatically restore missing modules when running CLI by @BernieWhite.
[#2552](https://github.com/microsoft/PSRule/issues/2552)
- Modules are automatically restored unless `--no-restore` is used with the `run` command.
- Engineering:
- Bump YamlDotNet to v16.1.3.
[#1874](https://github.com/microsoft/PSRule/pull/1874)
Expand Down
2 changes: 2 additions & 0 deletions docs/concepts/cli/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ The following commands are available in the CLI:

- [run](./run.md) — Run rules against an input path and output the results.
- [module](./module.md) — Manage or restore modules tracked by the module lock file and configured options.
- [restore](./restore.md) — Restore from the module lock file and configured options.
This is a shortcut for module restore.

## `--version`

Expand Down
29 changes: 29 additions & 0 deletions docs/concepts/cli/restore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: ps-rule restore command
---

# ps-rule restore

!!! Abstract
Use the `restore` command restore modules tracked by the module lock file and configured options. (`ps-rule.lock.json`).
This command is an alias for the `module restore` command.
The module lock file, provides consistent module versions across multiple machines and environments.
For more information, see [Lock file](../lockfile.md).

## Usage

```bash title="PSRule CLI command-line"
ps-rule restore [options]
```

## Options

### `--force`

Restore modules even when an existing version that meets constraints is already installed locally.

For example:

```bash title="PSRule CLI command-line"
ps-rule restore --force
```
5 changes: 5 additions & 0 deletions docs/concepts/cli/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ The name of one or more modules that contain rules or resources to use during a
The name of a specific baseline to use.
Currently, only a single baseline can be used during a run.

### `--no-restore`

Do not restore modules before running rules.
By default, modules are restored automatically before running rules.

### `--outcome`

Specifies the rule results to show in output.
Expand Down
12 changes: 6 additions & 6 deletions src/PSRule.CommandLine/ClientHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ public sealed class ClientHost : HostContext
private readonly ConsoleColor _ForegroundColor;

/// <summary>
///
/// Create a client host.
/// </summary>
/// <param name="context"></param>
/// <param name="verbose"></param>
/// <param name="debug"></param>
/// <param name="context">A client context.</param>
/// <param name="verbose">Enable or disable verbose log output.</param>
/// <param name="debug">Enable or disable debug log output.</param>
public ClientHost(ClientContext context, bool verbose, bool debug)
{
_Context = context;
Expand All @@ -33,11 +33,11 @@ public ClientHost(ClientContext context, bool verbose, bool debug)
_BackgroundColor = Console.BackgroundColor;
_ForegroundColor = Console.ForegroundColor;

Verbose($"Using working path: {Directory.GetCurrentDirectory()}");
Verbose($"[PSRule] -- Using working path: {Directory.GetCurrentDirectory()}");
}

/// <summary>
///
/// Handles preference variables.
/// </summary>
/// <param name="variableName"></param>
/// <returns></returns>
Expand Down
11 changes: 9 additions & 2 deletions src/PSRule.CommandLine/Commands/ModuleCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public static async Task<int> ModuleRestoreAsync(RestoreOptions operationOptions

using var pwsh = CreatePowerShell();

clientContext.LogVerbose("[PSRule][M] -- Determining modules to restore.");

// Restore from the lock file.
foreach (var kv in file.Modules)
{
Expand All @@ -63,7 +65,7 @@ public static async Task<int> ModuleRestoreAsync(RestoreOptions operationOptions
// clientContext.LogVerbose(Messages.UsingModule, module, targetVersion.ToString());
if (IsInstalled(pwsh, module, targetVersion, out var installedVersion) && !operationOptions.Force)
{
clientContext.LogVerbose($"The module {module} is already installed.");
clientContext.LogVerbose($"[PSRule][M] -- The module {module} is already installed.");
continue;
}

Expand Down Expand Up @@ -102,7 +104,7 @@ public static async Task<int> ModuleRestoreAsync(RestoreOptions operationOptions
(moduleConstraint == null || moduleConstraint.Accepts(installedVersion)))
{
// invocation.Log(Messages.UsingModule, includeModule, installedVersion.ToString());
clientContext.LogVerbose($"The module {includeModule} is already installed.");
clientContext.LogVerbose($"[PSRule][M] -- The module {includeModule} is already installed.");
continue;
}

Expand Down Expand Up @@ -130,6 +132,11 @@ public static async Task<int> ModuleRestoreAsync(RestoreOptions operationOptions
}

if (exitCode == 0)
{
clientContext.LogVerbose("[PSRule][M] -- All modules are restored and up-to-date.");
}

if (exitCode == 0 && operationOptions.WriteOutput)
{
ListModules(clientContext, GetModules(pwsh, file, clientContext.Option));
}
Expand Down
18 changes: 15 additions & 3 deletions src/PSRule.CommandLine/Commands/RunCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public sealed class RunCommand
/// <summary>
/// Call <c>run</c>.
/// </summary>
public static int Run(RunOptions operationOptions, ClientContext clientContext)
public static async Task<int> RunAsync(RunOptions operationOptions, ClientContext clientContext, CancellationToken cancellationToken = default)
{
var exitCode = 0;
var file = LockFile.Read(null);
Expand All @@ -41,7 +41,17 @@ public static int Run(RunOptions operationOptions, ClientContext clientContext)
if (operationOptions.Outcome != null && operationOptions.Outcome.Value != Rules.RuleOutcome.None)
clientContext.Option.Output.Outcome = operationOptions.Outcome;

// Build command
// Run restore command.
if (!operationOptions.NoRestore)
{
exitCode = await ModuleCommand.ModuleRestoreAsync(new RestoreOptions
{
Path = operationOptions.Path,
WriteOutput = false,
}, clientContext, cancellationToken);
}

// Build command.
var builder = CommandLineBuilder.Assert(operationOptions.Module ?? [], clientContext.Option, clientContext.Host, file);
builder.Baseline(BaselineOption.FromString(operationOptions.Baseline));
builder.InputPath(inputPath);
Expand All @@ -56,6 +66,8 @@ public static int Run(RunOptions operationOptions, ClientContext clientContext)
if (pipeline.Result.ShouldBreakFromFailure)
exitCode = ERROR_BREAK_ON_FAILURE;
}
return clientContext.Host.HadErrors || pipeline == null ? ERROR_GENERIC : exitCode;
exitCode = clientContext.Host.HadErrors || pipeline == null ? ERROR_GENERIC : exitCode;
clientContext.LogVerbose("[PSRule][R] -- Completed run with exit code {0}.", exitCode);
return await Task.FromResult(exitCode);
}
}
7 changes: 6 additions & 1 deletion src/PSRule.CommandLine/Models/RestoreOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace PSRule.CommandLine.Models;

/// <summary>
///
/// Options for the restore command.
/// </summary>
public sealed class RestoreOptions
{
Expand All @@ -17,4 +17,9 @@ public sealed class RestoreOptions
///
/// </summary>
public bool Force { get; set; }

/// <summary>
/// Write output from the restore operation.
/// </summary>
public bool WriteOutput { get; set; } = true;
}
7 changes: 6 additions & 1 deletion src/PSRule.CommandLine/Models/RunOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
namespace PSRule.CommandLine.Models;

/// <summary>
///
/// Options for the run command.
/// </summary>
public sealed class RunOptions
{
Expand Down Expand Up @@ -34,4 +34,9 @@ public sealed class RunOptions
///
/// </summary>
public string[]? InputPath { get; set; }

/// <summary>
/// Do not restore modules before running rules.
/// </summary>
public bool NoRestore { get; set; }
}
34 changes: 31 additions & 3 deletions src/PSRule.Tool/ClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ internal sealed class ClientBuilder
private readonly Option<string[]> _Run_Module;
private readonly Option<string> _Run_Baseline;
private readonly Option<string[]> _Run_Outcome;
private readonly Option<bool> _Run_NoRestore;

private ClientBuilder(RootCommand cmd)
{
Expand Down Expand Up @@ -87,6 +88,10 @@ private ClientBuilder(RootCommand cmd)
description: CmdStrings.Run_Outcome_Description
).FromAmong("Pass", "Fail", "Error", "Processed", "Problem");
_Run_Outcome.Arity = ArgumentArity.ZeroOrMore;
_Run_NoRestore = new Option<bool>(
"--no-restore",
description: CmdStrings.Run_NoRestore_Description
);

// Options for the module command.
_Module_Init_Force = new Option<bool>(
Expand Down Expand Up @@ -131,6 +136,7 @@ public static Command New()
var builder = new ClientBuilder(cmd);
builder.AddRun();
builder.AddModule();
builder.AddRestore();
return builder.Command;
}

Expand All @@ -147,7 +153,8 @@ private void AddRun()
cmd.AddOption(_Run_Module);
cmd.AddOption(_Run_Baseline);
cmd.AddOption(_Run_Outcome);
cmd.SetHandler((invocation) =>
cmd.AddOption(_Run_NoRestore);
cmd.SetHandler(async (invocation) =>
{
var option = new RunOptions
{
Expand All @@ -156,9 +163,10 @@ private void AddRun()
Module = invocation.ParseResult.GetValueForOption(_Run_Module),
Baseline = invocation.ParseResult.GetValueForOption(_Run_Baseline),
Outcome = ParseOutcome(invocation.ParseResult.GetValueForOption(_Run_Outcome)),
NoRestore = invocation.ParseResult.GetValueForOption(_Run_NoRestore),
};
var client = GetClientContext(invocation);
invocation.ExitCode = RunCommand.Run(option, client);
invocation.ExitCode = await RunCommand.RunAsync(option, client);
});
Command.AddCommand(cmd);
}
Expand Down Expand Up @@ -293,7 +301,6 @@ private void AddModule()

// Restore
var restore = new Command("restore", CmdStrings.Module_Restore_Description);
// restore.AddOption(_Path);
restore.AddOption(_Module_Restore_Force);
restore.SetHandler(async (invocation) =>
{
Expand All @@ -317,6 +324,27 @@ private void AddModule()
Command.AddCommand(cmd);
}

/// <summary>
/// Add the <c>restore</c> command.
/// </summary>
private void AddRestore()
{
var restore = new Command("restore", CmdStrings.Restore_Description);
restore.AddOption(_Module_Restore_Force);
restore.SetHandler(async (invocation) =>
{
var option = new RestoreOptions
{
Path = invocation.ParseResult.GetValueForOption(_Global_Path),
Force = invocation.ParseResult.GetValueForOption(_Module_Restore_Force),
};
var client = GetClientContext(invocation);
invocation.ExitCode = await ModuleCommand.ModuleRestoreAsync(option, client);
});

Command.AddCommand(restore);
}

private ClientContext GetClientContext(InvocationContext invocation)
{
var option = invocation.ParseResult.GetValueForOption(_Global_Option);
Expand Down
18 changes: 18 additions & 0 deletions src/PSRule.Tool/Resources/CmdStrings.Designer.cs

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

8 changes: 7 additions & 1 deletion src/PSRule.Tool/Resources/CmdStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -198,4 +198,10 @@
<data name="Module_Prerelease_Description" xml:space="preserve">
<value>Accept pre-release versions in addition to stable module versions.</value>
</data>
</root>
<data name="Restore_Description" xml:space="preserve">
<value>Restore from the module lock file and configured options. This is a shortcut for module restore.</value>
</data>
<data name="Run_NoRestore_Description" xml:space="preserve">
<value>Do not restore modules before running rules.</value>
</data>
</root>
2 changes: 1 addition & 1 deletion src/PSRule.Types/Environment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ internal static string GetRootedPath(string? path, bool normalize = false, strin
/// <remarks>
/// A base path always includes a trailing <c>/</c>.
/// </remarks>
internal static string GetRootedBasePath(string path, bool normalize = false, string? basePath = null)
public static string GetRootedBasePath(string path, bool normalize = false, string? basePath = null)
{
if (string.IsNullOrEmpty(path))
path = string.Empty;
Expand Down
Loading