Skip to content

Commit c8f8339

Browse files
authored
Merge pull request #2 from tablackburn/feature/v0.1.0-mvp
feat: v0.1.0 MVP - alerts list/get/ack/close on JSM Cloud
2 parents 10045f9 + 2866c3c commit c8f8339

36 files changed

Lines changed: 2239 additions & 310 deletions

.gitattributes

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
1+
# Normalize line endings to LF in both the repo and the working tree, regardless of
2+
# the contributor's global core.autocrlf setting. PowerShell on Windows handles LF
3+
# fine, and the repo already stores everything as LF in the index.
4+
* text=auto eol=lf
5+
16
# The docs are generated by the build script and should be considered artifacts
27
docs/en-US/* linguist-generated

.github/workflows/CI.yaml

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ jobs:
1818
steps:
1919
- uses: actions/checkout@v6
2020

21-
# Skip lint on the un-initialized template — the literal `./JsmOperations`
22-
# path argument can't be parsed by PowerShell (the double braces split into
23-
# mismatched script-block delimiters), so Invoke-ScriptAnalyzer fails with
24-
# a positional-argument error before it ever touches the folder. Same
25-
# marker as the unit-tests job below.
21+
# Template guard: skip downstream steps in the un-initialized
22+
# PowerShellModuleTemplate, where unresolved placeholders break
23+
# PSScriptAnalyzer's path argument. CHANGELOG.template.md exists only
24+
# pre-init and is renamed during initialization, so this guard is inert
25+
# in derived repos. Same marker as the unit-tests job below.
2626
- name: Detect template state
2727
id: template_guard
2828
shell: bash
@@ -80,13 +80,12 @@ jobs:
8080
steps:
8181
- uses: actions/checkout@v6
8282

83-
# Skip subsequent steps on the un-initialized template — Build's
84-
# GENERATEMARKDOWN task can't import the manifest while ed5ec128-55d1-4f39-bcbe-73a19f41367a is still
85-
# a literal placeholder. Marker: CHANGELOG.template.md exists only
86-
# pre-init; Initialize-Template.ps1 moves it onto CHANGELOG.md during
87-
# init, so downstream repos run the full job. The marker path contains
88-
# no placeholder token, so init's substitution loop leaves this guard
89-
# intact when the workflow is copied into a new module.
83+
# Template guard: skip subsequent steps in the un-initialized
84+
# PowerShellModuleTemplate, where Build's GENERATEMARKDOWN task can't
85+
# import the manifest while its GUID is still an unresolved placeholder.
86+
# Marker: CHANGELOG.template.md exists only pre-init;
87+
# Initialize-Template.ps1 renames it to CHANGELOG.md during init, so
88+
# this guard is inert in derived repos.
9089
# hashFiles() is not allowed in jobs.<job_id>.if, so we evaluate it in a
9190
# step and gate downstream steps on the resulting output.
9291
- name: Detect template state

.github/workflows/PublishModuleToPowerShellGallery.yaml

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ jobs:
2121
with:
2222
fetch-depth: 0
2323

24-
# Never publish the un-initialized template — the .psd1 still has
25-
# JsmOperations and ed5ec128-55d1-4f39-bcbe-73a19f41367a placeholders. Same marker as CI.yaml:
26-
# CHANGELOG.template.md exists only pre-init. hashFiles() is not allowed
27-
# in jobs.<job_id>.if, so we evaluate it in a step and gate the
28-
# version-detection and release-check steps on the resulting output;
29-
# everything downstream cascades on those steps' outputs and skips
30-
# naturally when they don't run.
24+
# Template guard: never publish from the un-initialized
25+
# PowerShellModuleTemplate, where the .psd1 still has unresolved
26+
# name/GUID placeholders. Same marker as CI.yaml: CHANGELOG.template.md
27+
# exists only pre-init and is renamed during initialization, so this
28+
# guard is inert in derived repos. hashFiles() is not allowed in
29+
# jobs.<job_id>.if, so we evaluate it in a step and gate version-
30+
# detection and release-check on the resulting output; everything
31+
# downstream cascades on those steps' outputs and skips naturally when
32+
# they don't run.
3133
- name: Detect template state
3234
id: template_guard
3335
shell: bash

CHANGELOG.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10-
## [0.1.0] - 2026-04-30
10+
## [0.1.0] - 2026-05-01
1111

1212
### Added
1313

14-
- Initial release
15-
- `Get-JsmExample` - Example public function
14+
- Initial release. JSM Cloud canonical backend; alerts list / get / acknowledge / close.
15+
- `Connect-JsmService` - establish in-memory connection (no on-disk persistence; see README for SecretManagement-based persistence).
16+
- `Disconnect-JsmService` - clear the active connection.
17+
- `Get-JsmConnection` - inspect the active connection (API token omitted).
18+
- `Get-JsmAlert` - list alerts (with optional Lucene query, sort, page size) or fetch one by id.
19+
- `Confirm-JsmAlert` - acknowledge an alert.
20+
- `Close-JsmAlert` - close an alert.

JsmOperations/JsmOperations.psd1

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ PowerShellVersion = '5.1'
5454
# RequiredModules = @()
5555

5656
# Assemblies that must be loaded prior to importing this module
57-
RequiredAssemblies = @()
57+
# RequiredAssemblies = @()
5858

5959
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
6060
# ScriptsToProcess = @()
@@ -69,9 +69,7 @@ RequiredAssemblies = @()
6969
# NestedModules = @()
7070

7171
# 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.
72-
FunctionsToExport = @(
73-
'Get-JsmExample'
74-
)
72+
FunctionsToExport = @()
7573

7674
# 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.
7775
CmdletsToExport = @()
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
function Invoke-JsmApi {
2+
<#
3+
.SYNOPSIS
4+
Internal HTTP transport for the JSM Cloud Operations API.
5+
6+
.DESCRIPTION
7+
Single private helper that every public JsmOperations cmdlet routes through.
8+
Reads the active connection from script-scoped state (set by Connect-JsmService),
9+
builds Basic authentication headers from the SecureString token, and invokes the REST call.
10+
11+
Not exported; not callable by module consumers.
12+
13+
.PARAMETER Method
14+
HTTP method. Currently only Get and Post are used by v0.1.0 cmdlets.
15+
16+
.PARAMETER Path
17+
API path relative to the connection's BaseUri (e.g. '/alerts' or "/alerts/$id/acknowledge").
18+
19+
.PARAMETER Body
20+
Optional hashtable. Serialized to JSON and sent as the request body.
21+
22+
.PARAMETER Query
23+
Optional hashtable of query-string parameters.
24+
25+
.EXAMPLE
26+
Invoke-JsmApi -Method Get -Path '/alerts' -Query @{ size = 5 }
27+
28+
Lists the first five alerts via the JSM Cloud canonical API.
29+
30+
.OUTPUTS
31+
System.Object
32+
Returns the deserialized response from Invoke-RestMethod.
33+
34+
.NOTES
35+
Throws a friendly error if no connection is active. HTTP errors propagate as-is.
36+
#>
37+
[CmdletBinding()]
38+
[OutputType([object])]
39+
param(
40+
[Parameter(Mandatory = $true)]
41+
[ValidateSet('Get', 'Post')]
42+
[string]
43+
$Method,
44+
45+
[Parameter(Mandatory = $true)]
46+
[ValidateNotNullOrEmpty()]
47+
[string]
48+
$Path,
49+
50+
[Parameter(Mandatory = $false)]
51+
[hashtable]
52+
$Body,
53+
54+
[Parameter(Mandatory = $false)]
55+
[hashtable]
56+
$Query
57+
)
58+
59+
begin {
60+
Write-Verbose 'Starting Invoke-JsmApi'
61+
if ($null -eq $script:JsmConnection) {
62+
throw 'No active JSM connection. Run Connect-JsmService before calling other JsmOperations cmdlets.'
63+
}
64+
}
65+
66+
process {
67+
try {
68+
$uri = $script:JsmConnection.BaseUri + $Path
69+
70+
if ($Query -and $Query.Count -gt 0) {
71+
$queryStringPairs = foreach ($entry in $Query.GetEnumerator()) {
72+
$encodedKey = [uri]::EscapeDataString($entry.Key)
73+
$encodedValue = [uri]::EscapeDataString([string]$entry.Value)
74+
"$encodedKey=$encodedValue"
75+
}
76+
$uri = $uri + '?' + ($queryStringPairs -join '&')
77+
}
78+
79+
$tokenPlain = [pscredential]::new(
80+
'jsm',
81+
$script:JsmConnection.ApiToken
82+
).GetNetworkCredential().Password
83+
$basicAuthCredential = "$($script:JsmConnection.Email):$tokenPlain"
84+
$basicAuthToken = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($basicAuthCredential))
85+
$headers = @{ Authorization = "Basic $basicAuthToken" }
86+
87+
$invokeParameters = @{
88+
Method = $Method
89+
Uri = $uri
90+
Headers = $headers
91+
ContentType = 'application/json'
92+
ErrorAction = 'Stop'
93+
}
94+
if ($Body -and $Body.Count -gt 0) {
95+
$invokeParameters.Body = ($Body | ConvertTo-Json -Depth 10 -Compress)
96+
}
97+
98+
Write-Verbose "Calling $Method $uri"
99+
$response = Invoke-RestMethod @invokeParameters
100+
Write-Output $response
101+
}
102+
catch {
103+
throw
104+
}
105+
}
106+
107+
end {
108+
Write-Verbose 'Completed Invoke-JsmApi'
109+
}
110+
}

JsmOperations/Private/Invoke-JsmHelper.ps1

Lines changed: 0 additions & 45 deletions
This file was deleted.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
function Close-JsmAlert {
2+
<#
3+
.SYNOPSIS
4+
Closes an alert in JSM Cloud Operations.
5+
6+
.DESCRIPTION
7+
Sends POST /v1/alerts/{id}/close. The close operation is asynchronous on
8+
the server side, so the response is a request-status object rather than
9+
the updated alert. Pass -Verbose to see the request URL.
10+
11+
Pipeline-friendly: pipe alerts (or their ids) directly in.
12+
13+
.PARAMETER Id
14+
The alert id (UUID or tinyId). Accepts pipeline input by value and by
15+
property name.
16+
17+
.PARAMETER Note
18+
An optional note attached to the close action. Visible in the alert's
19+
activity log.
20+
21+
.EXAMPLE
22+
Close-JsmAlert -Id 'abc-123-...'
23+
24+
Closes a single alert.
25+
26+
.EXAMPLE
27+
Get-JsmAlert -Query 'status:open AND tag:stale' | Close-JsmAlert -Note 'Auto-closed by stale-alert sweep'
28+
29+
Closes all open alerts tagged stale and stamps a note explaining why.
30+
31+
.OUTPUTS
32+
System.Management.Automation.PSCustomObject
33+
The asynchronous request-status object returned by the API.
34+
35+
.NOTES
36+
Close is async: the alert's status field will not update immediately.
37+
Re-fetch with Get-JsmAlert -Id to confirm.
38+
#>
39+
[CmdletBinding()]
40+
[OutputType([pscustomobject])]
41+
param(
42+
[Parameter(
43+
Mandatory = $true,
44+
ValueFromPipeline = $true,
45+
ValueFromPipelineByPropertyName = $true)]
46+
[ValidateNotNullOrEmpty()]
47+
[string]
48+
$Id,
49+
50+
[Parameter()]
51+
[ValidateNotNullOrEmpty()]
52+
[string]
53+
$Note
54+
)
55+
56+
begin {
57+
Write-Verbose 'Starting Close-JsmAlert'
58+
}
59+
60+
process {
61+
try {
62+
$body = @{}
63+
if ($PSBoundParameters.ContainsKey('Note')) {
64+
$body.note = $Note
65+
}
66+
$response = Invoke-JsmApi -Method 'Post' -Path "/alerts/$Id/close" -Body $body
67+
Write-Output $response
68+
}
69+
catch {
70+
throw
71+
}
72+
}
73+
74+
end {
75+
Write-Verbose 'Completed Close-JsmAlert'
76+
}
77+
}

0 commit comments

Comments
 (0)