Skip to content

Commit fb70d04

Browse files
committed
Merge branch 'main' into copilot/fix-2667
2 parents c78a726 + 5876eee commit fb70d04

File tree

9 files changed

+472
-49
lines changed

9 files changed

+472
-49
lines changed

.github/copilot-instructions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
Development Context:
22

33
- When implementing logging use `ILogger` from `src/PSRule.Types/Runtime/ILogger.cs`.
4+
- When creating new files, always add a trailing newline before the end of the file.

docs/changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ What's changed since pre-release v3.0.0-B0453:
4141
[#2892](https://github.com/microsoft/PSRule/issues/2892)
4242
- Added GitHub Actions support to CLI by @BernieWhite.
4343
[#2824](https://github.com/microsoft/PSRule/issues/2824)
44+
- Added Azure Pipelines support to CLI by @BernieWhite.
45+
[#2825](https://github.com/microsoft/PSRule/issues/2825)
4446
- Bump vscode engine to v1.102.0.
4547
[#2981](https://github.com/microsoft/PSRule/pull/2981)
4648
- Bug fixes:
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.CommandLine;
5+
using System.Diagnostics.CodeAnalysis;
6+
7+
namespace PSRule.Tool.Adapters;
8+
9+
/// <summary>
10+
/// Choose an appropriate adapter based on command line arguments.
11+
/// This is used to adapt the command line arguments for different CI/CD environments.
12+
/// </summary>
13+
internal sealed class AdapterBuilder
14+
{
15+
public static bool TryAdapter(string[] args, [NotNullWhen(true)] out Func<Task<int>>? execute)
16+
{
17+
execute = null;
18+
if (ShouldUseGitHubActionAdapter(args))
19+
{
20+
var adapter = new GitHubActionsAdapter();
21+
args = adapter.BuildArgs(args);
22+
23+
execute = async () =>
24+
{
25+
return await ClientBuilder.New().InvokeAsync(args);
26+
};
27+
}
28+
else if (ShouldUseAzurePipelinesAdapter(args))
29+
{
30+
var adapter = new AzurePipelinesAdapter();
31+
args = adapter.BuildArgs(args);
32+
33+
execute = async () =>
34+
{
35+
return await ClientBuilder.New().InvokeAsync(args);
36+
};
37+
}
38+
39+
return execute != null;
40+
}
41+
42+
private static bool ShouldUseGitHubActionAdapter(string[] args)
43+
{
44+
if (args == null || args.Length == 0)
45+
return false;
46+
47+
foreach (var arg in args)
48+
{
49+
if (arg.Equals("--in-github-actions", StringComparison.OrdinalIgnoreCase))
50+
return true;
51+
}
52+
return false;
53+
}
54+
55+
private static bool ShouldUseAzurePipelinesAdapter(string[] args)
56+
{
57+
if (args == null || args.Length == 0)
58+
return false;
59+
60+
foreach (var arg in args)
61+
{
62+
if (arg.Equals("--in-azure-pipelines", StringComparison.OrdinalIgnoreCase))
63+
return true;
64+
}
65+
return false;
66+
}
67+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace PSRule.Tool.Adapters;
5+
6+
/// <summary>
7+
/// This is an adapter for handling Azure Pipelines specific functionality
8+
/// for the official PSRule Azure Pipelines task.
9+
/// </summary>
10+
internal sealed class AzurePipelinesAdapter : CIAdapter
11+
{
12+
/// <summary>
13+
/// Load in environment variables from the Azure Pipelines task context.
14+
/// </summary>
15+
protected override string[] GetArgs(string[] args)
16+
{
17+
var result = new List<string>(args);
18+
19+
if (Environment.TryString("INPUT_INCLUDEPATH", out var includePath) && !string.IsNullOrWhiteSpace(includePath))
20+
{
21+
result.Add("--path");
22+
result.Add(includePath);
23+
WriteInput("IncludePath", includePath);
24+
}
25+
26+
if (Environment.TryString("INPUT_BASELINE", out var baseline) && !string.IsNullOrWhiteSpace(baseline))
27+
{
28+
result.Add("--baseline");
29+
result.Add(baseline);
30+
WriteInput("Baseline", baseline);
31+
}
32+
33+
if (Environment.TryStringArray("INPUT_CONVENTIONS", [COMMA], out var conventions) && conventions != null)
34+
{
35+
foreach (var convention in conventions)
36+
{
37+
if (string.IsNullOrWhiteSpace(convention))
38+
continue;
39+
40+
result.Add("--convention");
41+
result.Add(convention);
42+
43+
WriteInput("Convention", convention);
44+
}
45+
}
46+
47+
if (Environment.TryString("INPUT_INPUTPATH", out var inputPath) && !string.IsNullOrWhiteSpace(inputPath))
48+
{
49+
result.Add("--input-path");
50+
result.Add(inputPath);
51+
WriteInput("InputPath", inputPath);
52+
}
53+
54+
else if (Environment.TryStringArray("INPUT_FORMATS", [COMMA], out var formats) && formats != null)
55+
{
56+
foreach (var format in formats)
57+
{
58+
if (string.IsNullOrWhiteSpace(format))
59+
continue;
60+
61+
WriteInput("Format", format);
62+
}
63+
64+
result.Add("--formats");
65+
result.Add(string.Join(" ", formats));
66+
}
67+
68+
if (Environment.TryString("INPUT_OPTION", out var option) && !string.IsNullOrWhiteSpace(option))
69+
{
70+
result.Add("--option");
71+
result.Add(option);
72+
WriteInput("Option", option);
73+
}
74+
75+
if (Environment.TryString("INPUT_OUTCOME", out var outcome) && !string.IsNullOrWhiteSpace(outcome))
76+
{
77+
result.Add("--outcome");
78+
result.Add(outcome);
79+
WriteInput("Outcome", outcome);
80+
}
81+
82+
if (Environment.TryString("INPUT_OUTPUTFORMAT", out var outputFormat) && !string.IsNullOrWhiteSpace(outputFormat))
83+
{
84+
result.Add("--output");
85+
result.Add(outputFormat);
86+
WriteInput("OutputFormat", outputFormat);
87+
}
88+
89+
if (Environment.TryString("INPUT_OUTPUTPATH", out var outputPath) && !string.IsNullOrWhiteSpace(outputPath))
90+
{
91+
result.Add("--output-path");
92+
result.Add(outputPath);
93+
WriteInput("OutputPath", outputPath);
94+
}
95+
96+
if (Environment.TryString("INPUT_SUMMARY", out var summary) && !string.IsNullOrWhiteSpace(summary) && summary == "true")
97+
{
98+
// Azure Pipelines doesn't have a built-in job summary path like GitHub Actions
99+
// We'll use a default path for now
100+
var jobSummaryPath = "reports/summary.md";
101+
result.Add("--job-summary-path");
102+
result.Add(jobSummaryPath);
103+
WriteInput("Summary", summary);
104+
}
105+
106+
if (Environment.TryStringArray("INPUT_MODULES", [COMMA], out var modules) && modules != null)
107+
{
108+
foreach (var module in modules)
109+
{
110+
if (string.IsNullOrWhiteSpace(module))
111+
continue;
112+
113+
var m = module.Trim();
114+
115+
result.Add("--module");
116+
result.Add(m);
117+
118+
WriteInput("Module", m);
119+
}
120+
}
121+
122+
return [.. result];
123+
}
124+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace PSRule.Tool.Adapters;
5+
6+
/// <summary>
7+
/// Base class for CI adapters.
8+
/// Provides a common interface for CI-specific functionality.
9+
/// </summary>
10+
internal abstract class CIAdapter
11+
{
12+
protected const char COMMA = ',';
13+
14+
public string[] BuildArgs(string[] args)
15+
{
16+
WriteVersion();
17+
18+
args = GetArgs(args);
19+
20+
Console.WriteLine("");
21+
Console.WriteLine("---");
22+
23+
return args;
24+
}
25+
26+
protected abstract string[] GetArgs(string[] args);
27+
28+
protected void WriteVersion()
29+
{
30+
WriteInput("Version", ClientBuilder.Version!);
31+
}
32+
33+
protected static void WriteInput(string name, string value)
34+
{
35+
Console.WriteLine($"[info] Using {name}: {value}");
36+
}
37+
}

src/PSRule.Tool/Adapters/GitHubActionsAdapter.cs

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,12 @@ namespace PSRule.Tool.Adapters;
77
/// This is an adapter for handling GitHub Actions specific functionality
88
/// for the official PSRule GitHub Action.
99
/// </summary>
10-
internal sealed class GitHubActionsAdapter
10+
internal sealed class GitHubActionsAdapter : CIAdapter
1111
{
12-
private const char COMMA = ',';
13-
14-
public string[] BuildArgs(string[] args)
15-
{
16-
WriteVersion();
17-
18-
args = GetArgs(args);
19-
20-
Console.WriteLine("");
21-
Console.WriteLine("---");
22-
23-
return args;
24-
}
25-
26-
private void WriteVersion()
27-
{
28-
WriteInput("Version", ClientBuilder.Version!);
29-
}
30-
3112
/// <summary>
3213
/// Load in environment variables from the GitHub Action context.
3314
/// </summary>
34-
private string[] GetArgs(string[] args)
15+
protected override string[] GetArgs(string[] args)
3516
{
3617
var result = new List<string>(args);
3718

@@ -140,9 +121,4 @@ private string[] GetArgs(string[] args)
140121

141122
return [.. result];
142123
}
143-
144-
private static void WriteInput(string name, string value)
145-
{
146-
Console.WriteLine($"[info] Using {name}: {value}");
147-
}
148124
}

src/PSRule.Tool/Program.cs

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,14 @@ static async Task<int> Main(string[] args)
3131

3232
System.Environment.SetEnvironmentVariable("PSModulePath", modulePath, EnvironmentVariableTarget.Process);
3333

34-
var execute = async (string[] args) =>
34+
var execute = async () =>
3535
{
3636
return await ClientBuilder.New().InvokeAsync(args);
3737
};
3838

39-
if (ShouldUseGitHubActionAdapter(args))
39+
if (AdapterBuilder.TryAdapter(args, out var adapterExecute))
4040
{
41-
var adapter = new GitHubActionsAdapter();
42-
args = adapter.BuildArgs(args);
43-
44-
execute = async (string[] args) =>
45-
{
46-
return await ClientBuilder.New().InvokeAsync(args);
47-
};
41+
execute = adapterExecute;
4842
}
4943
else if (ShouldWaitForDebugger(args))
5044
{
@@ -54,20 +48,7 @@ static async Task<int> Main(string[] args)
5448
System.Diagnostics.Debugger.Break();
5549
}
5650

57-
return await execute(args);
58-
}
59-
60-
private static bool ShouldUseGitHubActionAdapter(string[] args)
61-
{
62-
if (args == null || args.Length == 0)
63-
return false;
64-
65-
foreach (var arg in args)
66-
{
67-
if (arg.Equals("--in-github-actions", StringComparison.OrdinalIgnoreCase))
68-
return true;
69-
}
70-
return false;
51+
return await execute();
7152
}
7253

7354
private static bool ShouldWaitForDebugger(string[] args)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace PSRule.Tool.Adapters;
5+
6+
public sealed class AdapterBuilderTests
7+
{
8+
[Fact]
9+
public void TryAdapter_WithGitHubActionsFlag_ReturnsTrue()
10+
{
11+
// Test that the --in-github-actions flag is detected
12+
var args = new string[] { "run", "--in-github-actions" };
13+
14+
Assert.True(AdapterBuilder.TryAdapter(args, out var execute));
15+
Assert.NotNull(execute);
16+
}
17+
18+
[Fact]
19+
public void TryAdapter_WithoutGitHubActionsFlag_ReturnsFalse()
20+
{
21+
// Test that without the flag, the adapter is not used
22+
var args = new string[] { "run" };
23+
24+
Assert.False(AdapterBuilder.TryAdapter(args, out var execute));
25+
Assert.Null(execute);
26+
}
27+
28+
[Fact]
29+
public void TryAdapter_WithAzurePipelinesFlag_ReturnsTrue()
30+
{
31+
// Test that the --in-azure-pipelines flag is detected
32+
var args = new string[] { "run", "--in-azure-pipelines" };
33+
34+
Assert.True(AdapterBuilder.TryAdapter(args, out var execute));
35+
Assert.NotNull(execute);
36+
}
37+
38+
[Fact]
39+
public void TryAdapter_WithoutAzurePipelinesFlag_ReturnsFalse()
40+
{
41+
// Test that without the flag, the adapter is not used
42+
var args = new string[] { "run" };
43+
44+
Assert.False(AdapterBuilder.TryAdapter(args, out var execute));
45+
Assert.Null(execute);
46+
}
47+
}

0 commit comments

Comments
 (0)