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
199 changes: 199 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# Copilot Instructions for AsBuiltReport.NetApp.ONTAP

## Project Overview

PowerShell 7 module that generates as-built documentation (HTML, Word, Text) for NetApp ONTAP storage clusters. It is part of the broader **AsBuiltReport** framework and uses **PScribo** for document rendering.

**Requires PowerShell 7+ (Core only — not compatible with Windows PowerShell 5.1).**

## Linting

Run PSScriptAnalyzer locally (mirrors the CI check):

```powershell
Invoke-ScriptAnalyzer -Path ./AsBuiltReport.NetApp.ONTAP/Src `
-Recurse `
-Settings ./.github/workflows/PSScriptAnalyzerSettings.psd1
```

Excluded rules (defined in `.github/workflows/PSScriptAnalyzerSettings.psd1`):
- `PSUseToExportFieldsInManifest`
- `PSReviewUnusedParameter`
- `PSUseDeclaredVarsMoreThanAssignments`
- `PSAvoidGlobalVars`

CI linting is in `.github/workflows/PSScriptAnalyzer.yml` (runs on every push/PR).

## Running the Report (Manual Testing)

There are no automated Pester tests. Testing is done by generating a real report against an ONTAP cluster:

```powershell
$Cred = Get-Credential
New-AsBuiltReport -Report NetApp.ONTAP `
-Target <cluster-mgmt-ip> `
-Credential $Cred `
-Format HTML `
-OutputFolderPath "$env:HOME/reports"
```

With health checks and all formats:

```powershell
New-AsBuiltReport -Report NetApp.ONTAP `
-Target <cluster-mgmt-ip> `
-Credential $Cred `
-Format HTML,Word `
-EnableHealthCheck `
-OutputFolderPath "$env:HOME/reports"
```

## Architecture

### Module Layout

```
AsBuiltReport.NetApp.ONTAP/
├── AsBuiltReport.NetApp.ONTAP.psd1 # Module manifest (required deps, version)
├── AsBuiltReport.NetApp.ONTAP.psm1 # Root module — auto-loads all Src/ files
├── AsBuiltReport.NetApp.ONTAP.json # Report configuration template (user-facing)
├── AsBuiltReport.NetApp.ONTAP.Style.ps1 # PScribo document styles
├── icons/ # PNG icons embedded in diagrams
└── Src/
├── Public/
│ └── Invoke-AsBuiltReport.NetApp.ONTAP.ps1 # Single exported entry point (985 lines)
└── Private/
├── Get-AbrOntap*.ps1 # 100+ data collection & rendering functions
├── ConvertTo-TextYN.ps1 # Boolean → Yes/No helper
├── ConvertTo-HashToYN.ps1 # Hashtable value → Yes/No helper
├── Export-AbrOntapDiagram.ps1 # Diagram export helper
└── Get-NetAppOntapAPI.ps1 # REST API wrapper
```

### Data Flow

1. **Entry point** (`Invoke-AsBuiltReport.NetApp.ONTAP.ps1`) validates PS version, connects to ONTAP via `Connect-NcController`, reads the JSON config, then calls each `Get-AbrOntap*` function in section order.
2. **Data collection** — each `Get-AbrOntap*` calls NetApp.ONTAP cmdlets (`Get-NcCluster`, `Get-NcAggr`, etc.) and populates `[ordered]` hashtables.
3. **Rendering** — collected data is passed to PScribo (`Table`, `Paragraph`, `Section`, `Heading*`). Tables use `Set-Style` for health-check color coding.
4. **Diagrams** — `Get-AbrOntapClusterDiagram` and similar functions use `AsBuiltReport.Diagram` to produce topology graphs; `Export-AbrOntapDiagram` writes them to disk.
5. **Output** — PScribo serializes to HTML/Word/Text via `New-Document`.

### Script-Scoped Variables (set by the framework, available in all Private functions)

| Variable | Content |
|---|---|
| `$Array` | Active `NcController` connection object |
| `$script:Report` | Report config from JSON (`ShowTableCaptions`, etc.) |
| `$script:InfoLevel` | Per-section detail level (0 = disabled, 1 = summary, 2 = advanced) |
| `$script:Options` | User options (diagram theme, excluded Vservers, etc.) |
| `$script:Healthcheck` | Health check enable/disable flags per component |
| `$script:TextInfo` | `CultureInfo` for text casing |
| `$script:Images` | Hashtable of icon paths for diagrams |

### InfoLevel Behavior

Every section is gated on `$InfoLevel.<Section>`:
- `0` — Skip entirely
- `1` — Summary view (key fields only)
- `2` — Advanced view (adds metrics, extended attributes, charts)

## Key Conventions

### Function Template

Every `Get-AbrOntap*` private function follows this structure:

```powershell
function Get-AbrOntap{Component}{Feature} {
<#
.SYNOPSIS
Used by As Built Report to retrieve NetApp ONTAP {description}
.NOTES
Version: 0.6.x
Author: Jonathan Colon
#>
[CmdletBinding()]
param ()

begin {
Write-PScriboMessage 'Collecting ONTAP {feature} information.'
}

process {
try {
$Data = Get-Nc{Feature} -Controller $Array
if ($Data) {
$OutObj = @()
$inObj = [ordered] @{
'Friendly Label' = $Data.Property ?? '--'
}
$OutObj += [pscustomobject](ConvertTo-HashToYN $inObj)

# Health check styling
if ($Healthcheck.{Component}.{Feature}) {
$OutObj | Where-Object { $_.'Status' -ne 'OK' } | Set-Style -Style Critical -Property 'Status'
}

$TableParams = @{
Name = "Table Title - $($Array.Name)"
List = $true
ColumnWidths = 25, 75
}
if ($Report.ShowTableCaptions) {
$TableParams['Caption'] = "- $($TableParams.Name)"
}
$OutObj | Table @TableParams
}
} catch {
Write-PScriboMessage -IsWarning $_.Exception.Message
}
}
}
```

### Naming

- **Private functions**: `Get-AbrOntap{Category}{Feature}` (e.g., `Get-AbrOntapStorageAGGR`, `Get-AbrOntapVserverVolumes`)
- **One function per file** — filename matches function name exactly
- **Local variables**: PascalCase (`$ClusterInfo`, `$OutObj`, `$inObj`)
- **Arrays**: plural PascalCase (`$Nodes`, `$Vservers`)
- Use `?? '--'` for null-coalescing to display placeholder in tables

### Formatting (enforced by `.vscode/settings.json`)

- 4-space indentation (no tabs)
- Opening brace on the same line (`if ($x) {`)
- Whitespace around operators, pipes, and separators
- Max line length: 115 characters
- No trailing whitespace
- Use full cmdlet names (no aliases — `ForEach-Object`, not `%`)
- Use correct casing for cmdlets (`Get-NcCluster`, not `get-nccluster`)

### PScribo Patterns

- `Section` / `Heading1`-`Heading5` for document structure
- `Table` with `ColumnWidths` always summing to 100
- `Set-Style` values: `OK`, `Warning`, `Critical`, `Info`
- `List = $true` for single-object detail views; `List = $false` for multi-row tables
- Always wrap table creation in `if ($Report.ShowTableCaptions)` before setting `Caption`

### Error Handling

- Wrap all data collection in `try/catch`
- Use `Write-PScriboMessage -IsWarning $_.Exception.Message` to log errors without aborting the report
- Non-fatal: missing data should result in the section being silently skipped (check with `if ($Data)`)

## Key Dependencies

| Module | Version | Role |
|---|---|---|
| `AsBuiltReport.Core` | ≥ 1.6.2 | Framework, PScribo integration |
| `NetApp.ONTAP` | ≥ 9.18.1.2601 | ONTAP API cmdlets (`Get-Nc*`) |
| `AsBuiltReport.Diagram` | ≥ 1.0.3 | Topology diagram generation |
| `AsBuiltReport.Chart` | ≥ 0.3.0 | Chart/graph generation |

Install all dependencies:

```powershell
Install-Module AsBuiltReport.Core, NetApp.ONTAP, AsBuiltReport.Diagram, AsBuiltReport.Chart
```
2 changes: 0 additions & 2 deletions .github/workflows/PSScriptAnalyzerSettings.psd1
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
@{
ExcludeRules = @(
'PSUseToExportFieldsInManifest',
'PSReviewUnusedParameter',
'PSUseDeclaredVarsMoreThanAssignments',
'PSAvoidGlobalVars'
)
}
104 changes: 44 additions & 60 deletions .github/workflows/Release.yml
Original file line number Diff line number Diff line change
@@ -1,65 +1,49 @@
name: Publish PowerShell Module

on:
release:
types: [published]
release:
types: [published]

jobs:
publish-to-gallery:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set PSRepository to Trusted for PowerShell Gallery
shell: pwsh
run: |
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
- name: Install AsBuiltReport.Core module
shell: pwsh
run: |
Install-Module -Name AsBuiltReport.Core -Repository PSGallery -Force
- name: Install NetApp.ONTAP module
shell: pwsh
run: |
Install-Module -Name NetApp.ONTAP -Repository PSGallery -Force
- name: Install AsBuiltReport.Chart module
shell: pwsh
run: |
Install-Module -Name AsBuiltReport.Chart -Repository PSGallery -Force
- name: Install AsBuiltReport.Diagram module
shell: pwsh
run: |
Install-Module -Name AsBuiltReport.Diagram -Repository PSGallery -Force
- name: Test Module Manifest
shell: pwsh
run: |
Test-ModuleManifest .\AsBuiltReport.NetApp.ONTAP\AsBuiltReport.NetApp.ONTAP.psd1
- name: Publish module to PowerShell Gallery
shell: pwsh
run: |
Publish-Module -Path ./AsBuiltReport.NetApp.ONTAP -NuGetApiKey ${{ secrets.PSGALLERY_API_KEY }} -Verbose
tweet:
needs: publish-to-gallery
runs-on: ubuntu-latest
steps:
- uses: Eomm/why-don-t-you-tweet@v2
# We don't want to tweet if the repository is not a public one
if: ${{ !github.event.repository.private }}
with:
# GitHub event payload
# https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#release
tweet-message: "[New Release] ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}! Check out what's new! ${{ github.event.release.html_url }} #Netapp #AsBuiltReport #PowerShell #Ontap #NetAppATeam"
env:
TWITTER_CONSUMER_API_KEY: ${{ secrets.TWITTER_CONSUMER_API_KEY }}
TWITTER_CONSUMER_API_SECRET: ${{ secrets.TWITTER_CONSUMER_API_SECRET }}
TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }}
TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
bsky-post:
needs: publish-to-gallery
runs-on: ubuntu-latest
steps:
- uses: zentered/bluesky-post-action@v0.3.0
with:
post: "[New Release] ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}! Check out what's new! ${{ github.event.release.html_url }} #Netapp #AsBuiltReport #PowerShell #Ontap #NetAppATeam"
env:
BSKY_IDENTIFIER: ${{ secrets.BSKY_IDENTIFIER }}
BSKY_PASSWORD: ${{ secrets.BSKY_PASSWORD }}
publish-to-gallery:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set PSRepository to Trusted for PowerShell Gallery
shell: pwsh
run: |
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
- name: Install AsBuiltReport.Core module
shell: pwsh
run: |
Install-Module -Name AsBuiltReport.Core -Repository PSGallery -Force
- name: Install NetApp.ONTAP module
shell: pwsh
run: |
Install-Module -Name NetApp.ONTAP -Repository PSGallery -Force
- name: Install AsBuiltReport.Chart module
shell: pwsh
run: |
Install-Module -Name AsBuiltReport.Chart -Repository PSGallery -Force
- name: Install AsBuiltReport.Diagram module
shell: pwsh
run: |
Install-Module -Name AsBuiltReport.Diagram -Repository PSGallery -Force
- name: Test Module Manifest
shell: pwsh
run: |
Test-ModuleManifest .\AsBuiltReport.NetApp.ONTAP\AsBuiltReport.NetApp.ONTAP.psd1
- name: Publish module to PowerShell Gallery
shell: pwsh
run: |
Publish-Module -Path ./AsBuiltReport.NetApp.ONTAP -NuGetApiKey ${{ secrets.PSGALLERY_API_KEY }} -Verbose
bsky-post:
needs: publish-to-gallery
runs-on: ubuntu-latest
steps:
- uses: zentered/bluesky-post-action@v0.4.0
with:
post: "[New Release] ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}! Check out what's new! ${{ github.event.release.html_url }} #Netapp #AsBuiltReport #PowerShell #Ontap #NetAppATeam"
env:
BSKY_IDENTIFIER: ${{ secrets.BSKY_IDENTIFIER }}
BSKY_PASSWORD: ${{ secrets.BSKY_PASSWORD }}
6 changes: 6 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "PowerShell: Module Interactive Session",
"type": "PowerShell",
"request": "launch",
"script": "import-Module -Name ./AsBuiltReport.NetApp.ONTAP/AsBuiltReport.NetApp.ONTAP.psd1"
},
{
"name": "PowerShell: Interactive Session",
"type": "PowerShell",
Expand Down
10 changes: 5 additions & 5 deletions AsBuiltReport.NetApp.ONTAP/AsBuiltReport.NetApp.ONTAP.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
RootModule = 'AsBuiltReport.NetApp.ONTAP.psm1'

# Version number of this module.
ModuleVersion = '0.6.13'
ModuleVersion = '0.6.14'

# Supported PSEditions
# CompatiblePSEditions = @()
Expand Down Expand Up @@ -54,19 +54,19 @@
RequiredModules = @(
@{
ModuleName = 'AsBuiltReport.Core';
ModuleVersion = '1.6.2'
ModuleVersion = '1.6.4'
},
@{
ModuleName = 'AsBuiltReport.Chart';
ModuleVersion = '0.3.0'
ModuleVersion = '0.3.2'
},
@{
ModuleName = 'NetApp.ONTAP';
ModuleVersion = '9.18.1.2601'
},
@{
ModuleName = 'AsBuiltReport.Diagram';
ModuleVersion = '1.0.3'
ModuleVersion = '1.0.7'
}
)

Expand All @@ -86,7 +86,7 @@
# NestedModules = @()

# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @('Invoke-AsBuiltReport.NetApp.ONTAP')
FunctionsToExport = @('Invoke-AsBuiltReport.NetApp.ONTAP', 'Get-AbrOntapLog')

# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
#CmdletsToExport = '*'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ function Export-AbrOntapDiagram {
$Graph = $DiagramObject
$Diagram = New-AbrDiagram @DiagramParams -InputObject $Graph
if ($Diagram) {
$BestAspectRatio = Get-BestImageAspectRatio -GraphObj $Diagram -MaxWidth 600
$BestAspectRatio = Get-BestImageAspectRatio -GraphObj $Diagram -MaxWidth 600 -MaxHeight 600
Section -Style Heading2 $MainDiagramLabel {
Image -Base64 $Diagram -Text 'NetApp Ontap Diagram' -Width $BestAspectRatio.Width -Height $BestAspectRatio.Height -Align Center
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ function Get-AbrOntapClusterReplicationDiagram {
'SourceVserver' = $VserverPeer.Vserver
'RemoteCluster' = $VserverPeer.PeerCluster
'RemoteVserver' = $VserverPeer.PeerVserver
'Color' = Get-RandomPastelColorHex
'Color' = Get-RandomPastelColorInHex
'SourceAdditionalInfo' = [PSCustomObject][ordered]@{
'Peer Vserver' = $VserverPeer.PeerVserver
'Peer Cluster' = $VserverPeer.PeerCluster
Expand Down
Loading