Skip to content

Commit 530d5d7

Browse files
authored
Merge pull request #1373 from SamErde/TagDocumentation
2 parents 9244721 + ad802ff commit 530d5d7

File tree

203 files changed

+1305
-1344
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

203 files changed

+1305
-1344
lines changed

.cspell.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
"version": "0.2",
77
"language": "en",
88
"words": [
9-
"Maester",
9+
"Contoso",
1010
"Docusaurus",
1111
"Entra",
12-
"Contoso",
12+
"Maester",
13+
"passwordless",
1314
"SSPR",
14-
"passwordless"
15+
"XSPM"
1516
],
1617
"ignoreWords": [
1718
"maester",
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: 🏷️ Update Tag Documentation
2+
3+
on:
4+
push:
5+
paths:
6+
- 'tests/**'
7+
- 'build/Update-TagsDocumentation.ps1'
8+
- '.github/workflows/update-tags.yml'
9+
10+
permissions:
11+
contents: write
12+
13+
jobs:
14+
update-tags:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v6
19+
20+
- name: Set up PowerShell modules
21+
run: |
22+
pwsh -NoLogo -NoProfile -Command "Install-Module Pester -Scope CurrentUser -Force -SkipPublisherCheck"
23+
24+
- name: Generate tags document
25+
run: pwsh -NoLogo -NoProfile -File build/Update-TagsDocumentation.ps1
26+
27+
- name: Commit and push changes
28+
run: |
29+
git config user.name "github-actions[bot]"
30+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
31+
if git diff --quiet -- website/docs/tags.md; then
32+
echo "No changes to commit."
33+
exit 0
34+
fi
35+
git add website/docs/tags.md
36+
git commit -m "chore: update tags documentation"
37+
git push
38+
env:
39+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

build/Update-TagsDocumentation.ps1

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
# Update-TagsDocumentation.ps1
2+
3+
<#
4+
.SYNOPSIS
5+
Updates the tags documentation file with an inventory of tags used in Maester tests.
6+
7+
.DESCRIPTION
8+
This script scans Maester's tests directory for tags used in Pester tests and generates a
9+
markdown documentation file. The document lists all tags along with their usage counts, grouped by categories.
10+
11+
.PARAMETER RepoRoot
12+
The path to the root of the repository. Defaults to the parent directory of the script location (i.e., the repository root).
13+
14+
.PARAMETER TestsPath
15+
The path to the Maester tests directory. Defaults to 'tests' within the repository root.
16+
17+
.PARAMETER TagsDocPath
18+
The path to the tags documentation file to update. Defaults to 'website/docs/tests/tags/readme.md' within the repository root.
19+
20+
.EXAMPLE
21+
.\Update-TagsDocumentation.ps1
22+
23+
Updates the tags documentation file using default paths for the repository root, tests directory, and tags documentation file.
24+
25+
.EXAMPLE
26+
.\Update-TagsDocumentation.ps1 -RepoRoot 'C:\Maester' -TestsPath 'C:\Maester\tests' -TagsDocPath 'C:\Maester\website\docs\tests\tags\readme.md'
27+
28+
Updates the tags documentation file using specified paths for the repository root, tests directory, and tags documentation file.
29+
#>
30+
[CmdletBinding()]
31+
param(
32+
# The path to the root of the repository.
33+
[Parameter()]
34+
[ValidateScript( { Test-Path $_ } )]
35+
[string]$RepoRoot = (Split-Path -Path $PSScriptRoot),
36+
37+
# The path to the Maester tests directory. Defaults to tests within the repository root.
38+
[Parameter()]
39+
[ValidateScript( { Test-Path $_ } )]
40+
[string]$TestsPath = (Join-Path -Path (Split-Path -Path $PSScriptRoot) -ChildPath 'tests'),
41+
42+
# The path to the tags documentation file to update. Defaults to website/docs/tests/tags/readme.md within the repository root.
43+
[Parameter()]
44+
[ValidateScript( { Test-Path (Split-Path $_ -Parent) } )]
45+
[string]$TagsDocPath = (Join-Path -Path (Split-Path -Path $PSScriptRoot) -ChildPath 'website/docs/tests/tags/readme.md')
46+
)
47+
48+
#region Get Tag Inventory
49+
# Dot-source the Get-MtTestInventory script (in lieu of importing the entire module).
50+
try {
51+
$InventoryScript = Join-Path $RepoRoot 'powershell/public/Get-MtTestInventory.ps1'
52+
. $InventoryScript
53+
} catch {
54+
throw "Failed to load Get-MtTestInventory.ps1 from $InventoryScript. $_"
55+
}
56+
57+
# Get test and tag inventory
58+
$Inventory = Get-MtTestInventory -Path $TestsPath
59+
60+
# Build list of tags counts for use as table rows.
61+
$TagCounts = [System.Collections.Generic.List[pscustomobject]]::new()
62+
foreach ($Item in $Inventory.GetEnumerator()) {
63+
$TagCounts.Add([pscustomobject]@{
64+
Tag = $Item.Name
65+
Count = $Item.Value.Count
66+
})
67+
}
68+
69+
function Add-OrUpdateTag {
70+
<#
71+
.SYNOPSIS
72+
Adds a new tag and its count to the list, or updates the count if the tag already exists in the list.
73+
#>
74+
param(
75+
# The tag to add or update in the list.
76+
[string]$Tag,
77+
78+
# The count to add to the tag's existing count (or set if new).
79+
[int]$Count
80+
)
81+
82+
# Check if the tag already exists in the TagCounts list.
83+
$Existing = $TagCounts | Where-Object { $_.Tag -eq $Tag }
84+
if ($Existing) {
85+
# Increment the count if it already exists in the list.
86+
foreach ($item in $Existing) {
87+
$item.Count += $Count
88+
}
89+
} else {
90+
# Add a new entry if the tag does not exist in the list.
91+
$TagCounts.Add([pscustomobject]@{
92+
Tag = $Tag
93+
Count = $Count
94+
})
95+
}
96+
} # end function Add-OrUpdateTag
97+
98+
#region Manually Add Tags
99+
# Manually add and count tags from tests that fail discovery so counts remain accurate.
100+
# For example, 'MT.1059' will always fail discovery unless connected to an environment that has implemented MDI.
101+
Add-OrUpdateTag -Tag 'MT.1059' -Count 1
102+
Add-OrUpdateTag -Tag 'MDI' -Count 1
103+
#endregion Manually Add Tags
104+
105+
# Define groups for categorizing tags in the documentation.
106+
$TagGroups = [ordered]@{
107+
'CIS' = { param($t) $t.Tag -match '^CIS(\.|\s|$)|L1|L2' }
108+
'CISA' = { param($t) $t.Tag -match '^CISA(\.|$)' -or $t.Tag -match '^MS\.' }
109+
'EIDSCA' = { param($t) $t.Tag -match '^EIDSCA(\.|$)' }
110+
'ORCA' = { param($t) $t.Tag -match '^ORCA(\.|$)' }
111+
'Maester' = { param($t) $t.Tag -match '^(MT\.|Maester)' }
112+
'Ungrouped' = { param($t) $t.Tag -notmatch '^(CIS|L1|L2|CISA|MS\.|EIDSCA|ORCA|MT\.|Maester)' }
113+
}
114+
#endregion Get Tag Inventory
115+
116+
117+
function ConvertTo-MarkdownTable {
118+
<#
119+
.SYNOPSIS
120+
Converts a list of tags and their counts into a markdown-formatted table.
121+
#>
122+
param(
123+
[string] $TagCategoryTitle,
124+
[System.Collections.Generic.List[PSCustomObject]] $Items
125+
)
126+
127+
# Return null if there are no items to process.
128+
if (-not $Items -or $Items.Count -eq 0) { return $null }
129+
130+
# Split items into multiple use (count > 1) and single use (count = 1) for the summary.
131+
$MultipleUse = $Items | Where-Object { $_.Count -gt 1 } | Sort-Object -Property Tag
132+
$SingleUse = $Items | Where-Object { $_.Count -eq 1 } | Sort-Object -Property Tag
133+
134+
# Initialize a StringBuilder to construct the markdown table.
135+
$SB = [System.Text.StringBuilder]::new()
136+
[void]$SB.AppendLine("### $TagCategoryTitle")
137+
[void]$SB.AppendLine()
138+
139+
# Only create table if there are tags used more than once.
140+
if ($MultipleUse) {
141+
[void]$SB.AppendLine('| Tag | Count |')
142+
[void]$SB.AppendLine('| :--- | ---: |')
143+
foreach ($i in $MultipleUse) {
144+
[void]$SB.AppendLine("| $($i.Tag) | $($i.Count) |")
145+
}
146+
[void]$SB.AppendLine()
147+
}
148+
149+
# Add single-use tags as comma-separated list
150+
if ($SingleUse) {
151+
$singleTagList = ($SingleUse | ForEach-Object { $_.Tag }) -join ', '
152+
[void]$SB.AppendLine("**Individual tags**: $singleTagList")
153+
#[void]$SB.AppendLine()
154+
}
155+
156+
# Return the constructed markdown string.
157+
return $SB.ToString()
158+
} # end function ConvertTo-MarkdownTable
159+
160+
# Build markdown sections for each tag group.
161+
$SectionBlocks = @()
162+
foreach ($Key in $TagGroups.Keys) {
163+
$matched = $TagCounts | Where-Object { & $TagGroups[$Key] $_ }
164+
if ($matched) { $SectionBlocks += ConvertTo-MarkdownTable -TagCategoryTitle $Key -Items ([System.Collections.Generic.List[PSCustomObject]]$matched) }
165+
}
166+
167+
# Join the section blocks into a single markdown string.
168+
$SectionsText = ($SectionBlocks | Where-Object { $_ }) -join "`n"
169+
170+
#region Create Static Markdown Content
171+
# Create the markdown front matter.
172+
$FrontMatter = @"
173+
---
174+
id: overview
175+
title: Tags Overview
176+
sidebar_label: 🏷️ Tags
177+
description: Overview of the tags used to identify and group related tests.
178+
---
179+
180+
"@
181+
182+
# Create the introductory text for the tags documentation.
183+
$Intro = @"
184+
## Tags Overview
185+
186+
Tags are used by Maester to identify and group related tests. They can also be used to select specific tests to run or exclude during test execution. This makes them very useful, but they can also get in the way if too many tags are created. Our goal is to minimize the "signal to noise" ratio when it comes to tags by focusing on a few key areas:
187+
188+
- **Test Suites**: We use standardized tag categories for test suites that align with well-known benchmarks and baselines. This helps users quickly identify tests that align with these widely recognized standards or with Maester's own suite of tests:
189+
- **CIS Benchmarks**: Tags prefixed with `CIS` (e.g., `CIS.M365.1.1`, `CIS.Azure.3.2`)
190+
- **CISA & Microsoft Baseline**: Tags prefixed with `CISA` or `MS` (e.g., `CISA.M365.Baseline`, `MS.Azure.Baseline`)
191+
- **EIDSCA**: Tags prefixed with `EIDSCA` (e.g., `EIDSCA.EntraID.2.1`)
192+
- **ORCA**: Tags prefixed with `ORCA` (e.g., `ORCA.Exchange.1.1`)
193+
- **Maester**: Tags prefixed with `Maester` or `MT` (e.g., `MT.1001`, `MT.1024`)
194+
195+
- **Product Areas**: Tags related to specific products and services that are being tested:
196+
- Azure
197+
- Defender XDR
198+
- Entra ID
199+
- Exchange
200+
- Microsoft 365
201+
- SharePoint
202+
- Teams
203+
204+
- **Practices or Capabilities**: Tags that denote specific security practices or capabilities within the security domain, such as:
205+
- Authentication (May include related topics such as MFA, SSPR, etc.)
206+
- Conditional Access (CA)
207+
- Data Loss Prevention (DLP)
208+
- Extended Security Posture Management (XSPM)
209+
- Hybrid Identity
210+
- Privileged Access Management (PAM)
211+
- Privileged Identity Management (PIM)
212+
213+
### Recommendations for Tag Usage
214+
215+
Less is more! When creating or assigning tags to tests, consider the following best practices:
216+
217+
1. Assign one ``Test Suite`` tag per test to ensure clarity on which benchmark or baseline the test aligns with. This tag will usually go in the `Describe` block of a Pester test file.
218+
2. Assign a ``Product Area`` tag to indicate which products or services the test is most relevant to. Limit these to 1-3 tags per test to avoid over-tagging.
219+
3. Use ``Practice`` or ``Capability`` tags sparingly and only when they add significant value in categorizing the test. Avoid creating overly specific tags that may only apply to a single test.
220+
221+
## Tags Used
222+
223+
The tables below list every tag discovered via `Get-MtTestInventory`.
224+
225+
"@
226+
#endregion Create Static Markdown Content
227+
228+
# Combine all parts into the final markdown content and write to the documentation file.
229+
$Body = ($FrontMatter + $Intro + "`n" + $SectionsText) -join "`n"
230+
231+
# Create the directory for the tags documentation file if it doesn't exist.
232+
if (-not (Test-Path -Path (Split-Path -Parent $TagsDocPath))) {
233+
New-Item -ItemType Directory -Path (Split-Path -Parent $TagsDocPath) -Force | Out-Null
234+
}
235+
236+
# Write the final markdown content to the tags documentation file.
237+
try {
238+
Set-Content -LiteralPath $TagsDocPath -Value $Body -Encoding UTF8
239+
Write-Host "Updated $TagsDocPath"
240+
}
241+
catch {
242+
Write-Error "Failed to write tags documentation to $TagsDocPath. $_"
243+
}

0 commit comments

Comments
 (0)