Merge pull request 'Rebrand to Grace Solutions; add README, about_ help, Gitea CI/CD, track Module bin' (#1) from dev into main
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
@@ -0,0 +1,296 @@
|
|||||||
|
name: Publish to PowerShell Gallery
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [closed]
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
if: github.event.pull_request.merged == true
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up .NET SDK
|
||||||
|
uses: actions/setup-dotnet@v4
|
||||||
|
with:
|
||||||
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
|
- name: Install PowerShell 7 (if not present)
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if command -v pwsh >/dev/null 2>&1; then
|
||||||
|
echo "pwsh already installed: $(pwsh --version)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y --no-install-recommends wget ca-certificates apt-transport-https gnupg
|
||||||
|
|
||||||
|
source /etc/os-release
|
||||||
|
wget -q "https://packages.microsoft.com/config/ubuntu/${VERSION_ID}/packages-microsoft-prod.deb" -O /tmp/ms-prod.deb
|
||||||
|
sudo dpkg -i /tmp/ms-prod.deb
|
||||||
|
rm -f /tmp/ms-prod.deb
|
||||||
|
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y powershell
|
||||||
|
pwsh --version
|
||||||
|
|
||||||
|
- name: Build and test module
|
||||||
|
shell: pwsh
|
||||||
|
run: ./build.ps1 -RunTests
|
||||||
|
|
||||||
|
- name: Validate module manifest
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$manifestPath = Join-Path $PWD 'Module/PSInfisicalAPI/PSInfisicalAPI.psd1'
|
||||||
|
$manifest = Test-ModuleManifest -Path $manifestPath
|
||||||
|
Write-Host "Manifest OK: $($manifest.Name) $($manifest.Version)"
|
||||||
|
|
||||||
|
- name: Upload module artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: PSInfisicalAPI-module
|
||||||
|
path: Module/PSInfisicalAPI
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
release:
|
||||||
|
needs: build
|
||||||
|
if: ${{ success() && github.event.pull_request.merged == true }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.meta.outputs.version }}
|
||||||
|
tag: ${{ steps.meta.outputs.tag }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Install PowerShell 7 (if not present)
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if command -v pwsh >/dev/null 2>&1; then
|
||||||
|
echo "pwsh already installed: $(pwsh --version)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y --no-install-recommends wget ca-certificates apt-transport-https gnupg
|
||||||
|
|
||||||
|
source /etc/os-release
|
||||||
|
wget -q "https://packages.microsoft.com/config/ubuntu/${VERSION_ID}/packages-microsoft-prod.deb" -O /tmp/ms-prod.deb
|
||||||
|
sudo dpkg -i /tmp/ms-prod.deb
|
||||||
|
rm -f /tmp/ms-prod.deb
|
||||||
|
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y powershell
|
||||||
|
pwsh --version
|
||||||
|
|
||||||
|
- name: Download module artifact
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: PSInfisicalAPI-module
|
||||||
|
path: Module/PSInfisicalAPI
|
||||||
|
|
||||||
|
- name: Resolve module version and tag
|
||||||
|
id: meta
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$manifestPath = Join-Path $PWD 'Module/PSInfisicalAPI/PSInfisicalAPI.psd1'
|
||||||
|
$manifest = Test-ModuleManifest -Path $manifestPath
|
||||||
|
$version = $manifest.Version.ToString()
|
||||||
|
$tag = $version
|
||||||
|
Write-Host "Module version: $version"
|
||||||
|
Write-Host "Release tag: $tag"
|
||||||
|
"version=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
|
||||||
|
"tag=$tag" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
|
||||||
|
|
||||||
|
- name: Package module as release asset
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
VERSION: ${{ steps.meta.outputs.version }}
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$zipPath = Join-Path $PWD "PSInfisicalAPI-$($env:VERSION).zip"
|
||||||
|
if (Test-Path $zipPath) { Remove-Item $zipPath -Force }
|
||||||
|
Compress-Archive -Path 'Module/PSInfisicalAPI/*' -DestinationPath $zipPath -Force
|
||||||
|
Write-Host "Created: $zipPath ($([math]::Round((Get-Item $zipPath).Length / 1KB, 1)) KB)"
|
||||||
|
|
||||||
|
- name: Create Gitea release
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ github.token }}
|
||||||
|
API_URL: ${{ github.api_url }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
TAG: ${{ steps.meta.outputs.tag }}
|
||||||
|
VERSION: ${{ steps.meta.outputs.version }}
|
||||||
|
COMMIT_SHA: ${{ github.sha }}
|
||||||
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
|
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
|
||||||
|
SERVER_URL: ${{ github.server_url }}
|
||||||
|
RUN_ID: ${{ github.run_id }}
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
if ([string]::IsNullOrWhiteSpace($env:GITEA_TOKEN)) {
|
||||||
|
throw "github.token is not available; cannot call the Gitea release API."
|
||||||
|
}
|
||||||
|
|
||||||
|
$shortSha = $env:COMMIT_SHA.Substring(0, [Math]::Min(12, $env:COMMIT_SHA.Length))
|
||||||
|
$buildUtc = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
|
||||||
|
$runUrl = "$($env:SERVER_URL)/$($env:REPO)/actions/runs/$($env:RUN_ID)"
|
||||||
|
$prUrl = "$($env:SERVER_URL)/$($env:REPO)/pulls/$($env:PR_NUMBER)"
|
||||||
|
|
||||||
|
$changelogSection = ''
|
||||||
|
if (Test-Path 'CHANGELOG.md') {
|
||||||
|
$lines = [System.IO.File]::ReadAllLines('CHANGELOG.md')
|
||||||
|
$start = -1; $end = $lines.Length
|
||||||
|
for ($i = 0; $i -lt $lines.Length; $i++) {
|
||||||
|
if ($lines[$i] -match "^##\s+$([regex]::Escape($env:VERSION))\s*$") { $start = $i + 1; continue }
|
||||||
|
if ($start -ge 0 -and $lines[$i] -match '^##\s+') { $end = $i; break }
|
||||||
|
}
|
||||||
|
if ($start -ge 0) {
|
||||||
|
$changelogSection = ($lines[$start..($end - 1)] -join "`n").Trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = @"
|
||||||
|
**PSInfisicalAPI $($env:VERSION)**
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| Version | ``$($env:VERSION)`` |
|
||||||
|
| Tag | ``$($env:TAG)`` |
|
||||||
|
| Commit | [``$shortSha``]($($env:SERVER_URL)/$($env:REPO)/commit/$($env:COMMIT_SHA)) |
|
||||||
|
| Built (UTC) | $buildUtc |
|
||||||
|
| Merged PR | [#$($env:PR_NUMBER) $($env:PR_TITLE)]($prUrl) by @$($env:PR_AUTHOR) |
|
||||||
|
| Workflow run | [$($env:RUN_ID)]($runUrl) |
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
$(if ($changelogSection) { $changelogSection } else { '_No CHANGELOG section found for this version._' })
|
||||||
|
|
||||||
|
## Install
|
||||||
|
``````powershell
|
||||||
|
Install-Module -Name PSInfisicalAPI -RequiredVersion $($env:VERSION) -Scope CurrentUser
|
||||||
|
``````
|
||||||
|
"@
|
||||||
|
|
||||||
|
$headers = @{
|
||||||
|
Authorization = "token $($env:GITEA_TOKEN)"
|
||||||
|
Accept = 'application/json'
|
||||||
|
}
|
||||||
|
$createUri = "$($env:API_URL)/repos/$($env:REPO)/releases"
|
||||||
|
|
||||||
|
$existing = $null
|
||||||
|
try {
|
||||||
|
$existing = Invoke-RestMethod -Method Get -Headers $headers `
|
||||||
|
-Uri "$createUri/tags/$($env:TAG)" -ErrorAction Stop
|
||||||
|
} catch {
|
||||||
|
if ($_.Exception.Response.StatusCode.value__ -ne 404) { throw }
|
||||||
|
}
|
||||||
|
if ($existing) {
|
||||||
|
Write-Host "Release tag '$($env:TAG)' already exists (id=$($existing.id)); skipping release creation."
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = @{
|
||||||
|
tag_name = $env:TAG
|
||||||
|
target_commitish = $env:COMMIT_SHA
|
||||||
|
name = "PSInfisicalAPI $($env:VERSION)"
|
||||||
|
body = $body
|
||||||
|
draft = $false
|
||||||
|
prerelease = $false
|
||||||
|
} | ConvertTo-Json -Depth 4
|
||||||
|
|
||||||
|
$release = Invoke-RestMethod -Method Post -Uri $createUri -Headers $headers `
|
||||||
|
-ContentType 'application/json' -Body $payload
|
||||||
|
Write-Host "Created release id=$($release.id) at $($release.html_url)"
|
||||||
|
|
||||||
|
$assetPath = Join-Path $PWD "PSInfisicalAPI-$($env:VERSION).zip"
|
||||||
|
$uploadUri = "$createUri/$($release.id)/assets?name=PSInfisicalAPI-$($env:VERSION).zip"
|
||||||
|
$fileBytes = [System.IO.File]::ReadAllBytes($assetPath)
|
||||||
|
Invoke-RestMethod -Method Post -Uri $uploadUri -Headers $headers `
|
||||||
|
-ContentType 'application/zip' -Body $fileBytes | Out-Null
|
||||||
|
Write-Host "Uploaded asset: PSInfisicalAPI-$($env:VERSION).zip"
|
||||||
|
|
||||||
|
publish:
|
||||||
|
needs: release
|
||||||
|
if: ${{ success() && github.event.pull_request.merged == true }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Install PowerShell 7 (if not present)
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if command -v pwsh >/dev/null 2>&1; then
|
||||||
|
echo "pwsh already installed: $(pwsh --version)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y --no-install-recommends wget ca-certificates apt-transport-https gnupg
|
||||||
|
|
||||||
|
source /etc/os-release
|
||||||
|
wget -q "https://packages.microsoft.com/config/ubuntu/${VERSION_ID}/packages-microsoft-prod.deb" -O /tmp/ms-prod.deb
|
||||||
|
sudo dpkg -i /tmp/ms-prod.deb
|
||||||
|
rm -f /tmp/ms-prod.deb
|
||||||
|
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y powershell
|
||||||
|
pwsh --version
|
||||||
|
|
||||||
|
- name: Download module artifact
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: PSInfisicalAPI-module
|
||||||
|
path: Module/PSInfisicalAPI
|
||||||
|
|
||||||
|
- name: Bootstrap PowerShellGet / NuGet provider
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
|
||||||
|
if (-not (Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue)) {
|
||||||
|
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser | Out-Null
|
||||||
|
}
|
||||||
|
Get-PackageProvider -Name NuGet | Format-Table Name,Version
|
||||||
|
|
||||||
|
- name: Verify PowerShell Gallery API key is configured
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
|
||||||
|
run: |
|
||||||
|
if ([string]::IsNullOrWhiteSpace($env:PSGALLERY_API_KEY)) {
|
||||||
|
throw "Repository secret 'PSGALLERY_API_KEY' is not configured."
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Re-validate downloaded module manifest
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$manifestPath = Join-Path $PWD 'Module/PSInfisicalAPI/PSInfisicalAPI.psd1'
|
||||||
|
$manifest = Test-ModuleManifest -Path $manifestPath
|
||||||
|
Write-Host "Manifest OK: $($manifest.Name) $($manifest.Version)"
|
||||||
|
|
||||||
|
- name: Publish to PowerShell Gallery
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$moduleDir = Join-Path $PWD 'Module/PSInfisicalAPI'
|
||||||
|
Write-Host "Publishing module from: $moduleDir"
|
||||||
|
Publish-Module `
|
||||||
|
-Path $moduleDir `
|
||||||
|
-NuGetApiKey $env:PSGALLERY_API_KEY `
|
||||||
|
-Verbose
|
||||||
+6
-2
@@ -4,8 +4,9 @@ obj/
|
|||||||
Artifacts/
|
Artifacts/
|
||||||
Releases/
|
Releases/
|
||||||
|
|
||||||
## Module bin output is generated by build.ps1
|
## Module bin output is generated by build.ps1, but tracked so the module is consumable from source
|
||||||
Module/PSInfisicalAPI/bin/
|
!Module/PSInfisicalAPI/bin/
|
||||||
|
!Module/PSInfisicalAPI/bin/**
|
||||||
|
|
||||||
## VS / Rider / VSCode
|
## VS / Rider / VSCode
|
||||||
.vs/
|
.vs/
|
||||||
@@ -28,3 +29,6 @@ Thumbs.db
|
|||||||
TestResults/
|
TestResults/
|
||||||
*.trx
|
*.trx
|
||||||
*.coverage
|
*.coverage
|
||||||
|
|
||||||
|
## Local helper scripts (not part of the module)
|
||||||
|
scripts/
|
||||||
|
|||||||
+796
@@ -6,24 +6,820 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.03.0131
|
||||||
|
|
||||||
|
- Build produced from commit 7be0b7b42008.
|
||||||
|
- **Behavior change**: `Get-InfisicalSecrets` and `Get-InfisicalSecret` now default `-ViewSecretValue` to `$true`. Real secret values are returned by default. To request the redacted/hidden response, pass `-ViewSecretValue:$false`.
|
||||||
|
- `InfisicalSecretMapper` now treats the server-side `<hidden-by-infisical>` placeholder as a hidden marker rather than a value: when `secretValueHidden=true` (or the placeholder string is detected) `SecretValue` is set to `null` instead of stuffing the literal into a `SecureString`. This prevents downstream consumers (auth, exports, dictionary conversion) from silently using `<hidden-by-infisical>` as if it were a real secret.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.03.0113
|
||||||
|
|
||||||
|
- Build produced from commit 09c577ebd0fd.
|
||||||
|
- Added `InfisicalSecret.GetPlainTextValue()` for direct plain-text access to secret material from PowerShell without needing `Marshal.SecureStringToBSTR`.
|
||||||
|
- Added `-AsPlainText` switch to `ConvertTo-InfisicalSecretDictionary`; when present the cmdlet emits `Dictionary<string, string>` instead of the default `Dictionary<string, SecureString>`.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.03.0057
|
||||||
|
|
||||||
|
- Build produced from commit 7e5209190ac2.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.03.0056
|
||||||
|
|
||||||
|
- Build produced from commit 7e5209190ac2.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.03.0055
|
||||||
|
|
||||||
|
- Build produced from commit 7e5209190ac2.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.03.0047
|
||||||
|
|
||||||
|
- Build produced from commit 7e5209190ac2.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.03.0046
|
||||||
|
|
||||||
|
- Build produced from commit 7e5209190ac2.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.03.0032
|
||||||
|
|
||||||
|
- Build produced from commit c86676010532.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1737
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1724
|
||||||
|
|
||||||
|
- Build produced from commit 5801b4774af5.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1737
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward) (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1648
|
||||||
|
|
||||||
|
- Build produced from commit 430e3a00c921.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1737
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1724
|
||||||
|
|
||||||
|
- Build produced from commit 5801b4774af5.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1737
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward) (carried forward) (carried forward) (carried forward)
|
||||||
|
|
||||||
## 2026.06.02.1638
|
## 2026.06.02.1638
|
||||||
|
|
||||||
- Build produced from commit 3c47d6ff30ec.
|
- Build produced from commit 3c47d6ff30ec.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
## Unreleased (carried forward)
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1737
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1724
|
||||||
|
|
||||||
|
- Build produced from commit 5801b4774af5.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1737
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward) (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1648
|
||||||
|
|
||||||
|
- Build produced from commit 430e3a00c921.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1737
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1724
|
||||||
|
|
||||||
|
- Build produced from commit 5801b4774af5.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1737
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward) (carried forward) (carried forward) (carried forward) (carried forward)
|
||||||
|
|
||||||
## 2026.06.02.1611
|
## 2026.06.02.1611
|
||||||
|
|
||||||
- Build produced from commit 3c47d6ff30ec.
|
- Build produced from commit 3c47d6ff30ec.
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1737
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1724
|
||||||
|
|
||||||
|
- Build produced from commit 5801b4774af5.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1737
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward) (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1648
|
||||||
|
|
||||||
|
- Build produced from commit 430e3a00c921.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1737
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1724
|
||||||
|
|
||||||
|
- Build produced from commit 5801b4774af5.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1737
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward) (carried forward) (carried forward) (carried forward)
|
||||||
|
|
||||||
## 2026.06.02.1638
|
## 2026.06.02.1638
|
||||||
|
|
||||||
- Build produced from commit 3c47d6ff30ec.
|
- Build produced from commit 3c47d6ff30ec.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
## Unreleased (carried forward) (carried forward)
|
## Unreleased (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1737
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1724
|
||||||
|
|
||||||
|
- Build produced from commit 5801b4774af5.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1737
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward) (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1648
|
||||||
|
|
||||||
|
- Build produced from commit 430e3a00c921.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1737
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1724
|
||||||
|
|
||||||
|
- Build produced from commit 5801b4774af5.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1737
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.02.1902
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.02.1907
|
||||||
|
|
||||||
|
- Build produced from commit fa65c18bc171.
|
||||||
|
|
||||||
|
## Unreleased (carried forward) (carried forward) (carried forward) (carried forward) (carried forward) (carried forward) (carried forward)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Initial repository skeleton, C# `netstandard2.0` project, and PowerShell module layout.
|
- Initial repository skeleton, C# `netstandard2.0` project, and PowerShell module layout.
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
@{
|
@{
|
||||||
RootModule = 'PSInfisicalAPI.psm1'
|
RootModule = 'PSInfisicalAPI.psm1'
|
||||||
ModuleVersion = '2026.06.02.1638'
|
ModuleVersion = '2026.06.03.0131'
|
||||||
GUID = 'b8a2f3d4-7c51-4d2f-9e6a-1f0c8b3d4e51'
|
GUID = 'b8a2f3d4-7c51-4d2f-9e6a-1f0c8b3d4e51'
|
||||||
Author = 'Alphaeus Mote'
|
Author = 'Grace Solutions'
|
||||||
CompanyName = ''
|
CompanyName = 'Grace Solutions'
|
||||||
Copyright = '(c) Alphaeus Mote. All rights reserved.'
|
Copyright = '(c) Grace Solutions. All rights reserved.'
|
||||||
Description = 'PSInfisicalAPI is a C# binary PowerShell module for the Infisical REST API.'
|
Description = 'PSInfisicalAPI is a C# binary PowerShell module for the Infisical REST API, providing cmdlets for authentication, secret retrieval, and export with automatic environment-variable discovery across Process, User, and Machine scopes.'
|
||||||
PowerShellVersion = '5.1'
|
PowerShellVersion = '5.1'
|
||||||
CompatiblePSEditions = @('Desktop','Core')
|
CompatiblePSEditions = @('Desktop','Core')
|
||||||
FunctionsToExport = @()
|
FunctionsToExport = @()
|
||||||
@@ -23,10 +23,11 @@
|
|||||||
TypesToProcess = @('PSInfisicalAPI.Types.ps1xml')
|
TypesToProcess = @('PSInfisicalAPI.Types.ps1xml')
|
||||||
PrivateData = @{
|
PrivateData = @{
|
||||||
PSData = @{
|
PSData = @{
|
||||||
Tags = @('Infisical','Secrets','API','SecureString')
|
Tags = @('Infisical','Secrets','API','SecureString','Vault','Authentication')
|
||||||
ProjectUri = ''
|
LicenseUri = 'https://www.gnu.org/licenses/agpl-3.0.html'
|
||||||
ReleaseNotes = ''
|
ProjectUri = 'https://prod.git.gracesolution.info/gsadmin/PSInfisicalAPI'
|
||||||
CommitHash = '3c47d6ff30ec'
|
ReleaseNotes = 'See CHANGELOG.md in the project repository for release history.'
|
||||||
|
CommitHash = '7be0b7b42008'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,97 @@
|
|||||||
|
TOPIC
|
||||||
|
about_PSInfisicalAPI
|
||||||
|
|
||||||
|
SHORT DESCRIPTION
|
||||||
|
PSInfisicalAPI is a C# binary PowerShell module that exposes cmdlets for
|
||||||
|
authenticating against and retrieving secrets from the Infisical REST API,
|
||||||
|
with automatic environment-variable discovery for connection parameters.
|
||||||
|
|
||||||
|
LONG DESCRIPTION
|
||||||
|
The module provides the following cmdlets:
|
||||||
|
|
||||||
|
Connect-Infisical Establish a session.
|
||||||
|
Disconnect-Infisical Clear the current session.
|
||||||
|
Get-InfisicalSecrets List secrets at a path.
|
||||||
|
Get-InfisicalSecret Retrieve a single secret by name.
|
||||||
|
ConvertTo-InfisicalSecretDictionary Convert secret objects to a hashtable.
|
||||||
|
Export-InfisicalSecrets Export secrets to JSON, YAML, XML, or .env.
|
||||||
|
|
||||||
|
Use Get-Help <cmdlet> -Full for parameter details.
|
||||||
|
|
||||||
|
AUTHENTICATION
|
||||||
|
Connect-Infisical supports two parameter sets:
|
||||||
|
|
||||||
|
UniversalAuth -ClientId / -ClientSecret (SecureString)
|
||||||
|
Token -AccessToken (SecureString)
|
||||||
|
|
||||||
|
Common parameters apply to both sets:
|
||||||
|
-BaseUri, -OrganizationId, -ProjectId, -Environment,
|
||||||
|
-SecretPath (default '/'), -ApiVersion (default 'v4'), -PassThru.
|
||||||
|
|
||||||
|
ENVIRONMENT VARIABLE DISCOVERY
|
||||||
|
When any of the Connect-Infisical parameters are omitted, null, or contain
|
||||||
|
only whitespace, the cmdlet scans environment variables in three scopes,
|
||||||
|
in order:
|
||||||
|
|
||||||
|
1. Process
|
||||||
|
2. User
|
||||||
|
3. Machine
|
||||||
|
|
||||||
|
The first matching variable with a non-blank value is used. Explicitly
|
||||||
|
supplied parameter values always win.
|
||||||
|
|
||||||
|
Patterns are case-insensitive and match Infisical's CLI defaults plus
|
||||||
|
common variants such as CLOUDINIT_INFISICAL_* and custom-prefixed names
|
||||||
|
(for example "myapp_infisical_client_id").
|
||||||
|
|
||||||
|
Parameter Example variable names
|
||||||
|
---------------- ----------------------------------------------------------
|
||||||
|
BaseUri INFISICAL_API_URL, INFISICAL_BASE_URL, INFISICAL_HOST
|
||||||
|
OrganizationId INFISICAL_ORG_ID, INFISICAL_ORGANIZATION_ID
|
||||||
|
ProjectId INFISICAL_PROJECT_ID, INFISICAL_WORKSPACE_ID
|
||||||
|
Environment INFISICAL_ENVIRONMENT, INFISICAL_ENV, INFISICAL_ENV_SLUG
|
||||||
|
ClientId INFISICAL_CLIENT_ID, INFISICAL_UNIVERSAL_AUTH_CLIENT_ID
|
||||||
|
ClientSecret INFISICAL_CLIENT_SECRET, INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET
|
||||||
|
AccessToken INFISICAL_TOKEN, INFISICAL_ACCESS_TOKEN, INFISICAL_AUTH_TOKEN
|
||||||
|
SecretPath INFISICAL_SECRET_PATH, INFISICAL_DEFAULT_SECRET_PATH
|
||||||
|
ApiVersion INFISICAL_API_VERSION
|
||||||
|
|
||||||
|
Sensitive values (ClientSecret, AccessToken) are read directly into a
|
||||||
|
read-only SecureString. Discovered values are never written to logs;
|
||||||
|
only the variable name and the scope it was found in are recorded.
|
||||||
|
|
||||||
|
Use -Verbose to see the scan announcement and any discovered variable
|
||||||
|
names.
|
||||||
|
|
||||||
|
EXAMPLES
|
||||||
|
Example 1: Zero-configuration connect (all values from environment).
|
||||||
|
|
||||||
|
Connect-Infisical
|
||||||
|
Get-InfisicalSecrets
|
||||||
|
|
||||||
|
Example 2: Explicit parameters override discovery.
|
||||||
|
|
||||||
|
$secret = Read-Host -AsSecureString 'Client Secret'
|
||||||
|
Connect-Infisical -Environment prod -ClientSecret $secret
|
||||||
|
|
||||||
|
Example 3: Token-based authentication.
|
||||||
|
|
||||||
|
$token = Read-Host -AsSecureString 'Access Token'
|
||||||
|
Connect-Infisical -AccessToken $token
|
||||||
|
|
||||||
|
Example 4: Export to a .env file.
|
||||||
|
|
||||||
|
Get-InfisicalSecrets |
|
||||||
|
Export-InfisicalSecrets -Path .\secrets.env -Format Env
|
||||||
|
|
||||||
|
SECURITY NOTES
|
||||||
|
- SecureString is used for ClientSecret, AccessToken, and any secret
|
||||||
|
payloads returned by the API.
|
||||||
|
- Sanitization is applied before any value reaches the logging pipeline.
|
||||||
|
- Sessions are stored in process-local state only and never persisted.
|
||||||
|
|
||||||
|
SEE ALSO
|
||||||
|
Get-Help Connect-Infisical -Full
|
||||||
|
Get-Help Get-InfisicalSecrets -Full
|
||||||
|
Get-Help Export-InfisicalSecrets -Full
|
||||||
|
https://infisical.com/docs/api-reference/overview/introduction
|
||||||
@@ -1,2 +1,128 @@
|
|||||||
# PSInfisicalAPI
|
# PSInfisicalAPI
|
||||||
|
|
||||||
|
A C# binary PowerShell module for interacting with the [Infisical](https://infisical.com/) REST API. It provides cmdlets for authentication, secret retrieval, structured export, and includes automatic environment-variable discovery so connections can be established with little or no inline configuration.
|
||||||
|
|
||||||
|
- License: AGPL-3.0
|
||||||
|
- Author: Grace Solutions
|
||||||
|
- Target framework: .NET Standard 2.0 (compatible with Windows PowerShell 5.1 and PowerShell 7+)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### From the PowerShell Gallery
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Install-Module -Name PSInfisicalAPI -Scope CurrentUser
|
||||||
|
Import-Module -Name PSInfisicalAPI
|
||||||
|
```
|
||||||
|
|
||||||
|
### From source
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git clone https://prod.git.gracesolution.info/gsadmin/PSInfisicalAPI.git
|
||||||
|
cd PSInfisicalAPI
|
||||||
|
pwsh -NoProfile -ExecutionPolicy Bypass -File .\build.ps1 -RunTests
|
||||||
|
Import-Module -Name .\Module\PSInfisicalAPI
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cmdlets
|
||||||
|
|
||||||
|
| Cmdlet | Purpose |
|
||||||
|
| ------------------------------------- | -------------------------------------------------------------------------- |
|
||||||
|
| `Connect-Infisical` | Establish a session using Universal Auth or a pre-issued access token. |
|
||||||
|
| `Disconnect-Infisical` | Clear the current session. |
|
||||||
|
| `Get-InfisicalSecrets` | List secrets at a given path / environment. |
|
||||||
|
| `Get-InfisicalSecret` | Retrieve a single secret by name. |
|
||||||
|
| `ConvertTo-InfisicalSecretDictionary` | Convert secret objects into a `Hashtable` keyed by `SecretKey`. |
|
||||||
|
| `Export-InfisicalSecrets` | Export secrets to JSON, YAML, XML, or `.env` format. |
|
||||||
|
|
||||||
|
Use `Get-Help <Cmdlet> -Full` for parameter details and `Get-Help about_PSInfisicalAPI` for the module overview.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$secureSecret = Read-Host -AsSecureString 'Client Secret'
|
||||||
|
|
||||||
|
$connection = Connect-Infisical `
|
||||||
|
-BaseUri 'https://app.infisical.com' `
|
||||||
|
-OrganizationId '00000000-0000-0000-0000-000000000000' `
|
||||||
|
-ProjectId '11111111-1111-1111-1111-111111111111' `
|
||||||
|
-Environment 'dev' `
|
||||||
|
-ClientId 'machine-identity-client-id' `
|
||||||
|
-ClientSecret $secureSecret `
|
||||||
|
-PassThru
|
||||||
|
|
||||||
|
Get-InfisicalSecrets -SecretPath '/'
|
||||||
|
Disconnect-Infisical
|
||||||
|
```
|
||||||
|
|
||||||
|
## Automatic environment-variable discovery
|
||||||
|
|
||||||
|
When `Connect-Infisical` is invoked with one or more parameters missing (or set to whitespace/empty), the cmdlet searches environment variables and uses the first value it finds. This makes invocation as simple as `Connect-Infisical` when variables are set up in advance.
|
||||||
|
|
||||||
|
### Scope precedence
|
||||||
|
|
||||||
|
Scopes are searched in order; the first matching variable with a non-blank value wins:
|
||||||
|
|
||||||
|
1. `Process`
|
||||||
|
2. `User`
|
||||||
|
3. `Machine`
|
||||||
|
|
||||||
|
### Patterns
|
||||||
|
|
||||||
|
The resolver matches case-insensitively against patterns aligned with Infisical's CLI defaults plus common variants such as `CLOUDINIT_INFISICAL_*` and custom-prefixed names (e.g., `myapp_infisical_client_id`).
|
||||||
|
|
||||||
|
| Parameter | Example variable names matched |
|
||||||
|
| ----------------- | ------------------------------------------------------------------------------------ |
|
||||||
|
| `BaseUri` | `INFISICAL_API_URL`, `INFISICAL_BASE_URL`, `INFISICAL_HOST` |
|
||||||
|
| `OrganizationId` | `INFISICAL_ORG_ID`, `INFISICAL_ORGANIZATION_ID` |
|
||||||
|
| `ProjectId` | `INFISICAL_PROJECT_ID`, `INFISICAL_WORKSPACE_ID` |
|
||||||
|
| `Environment` | `INFISICAL_ENVIRONMENT`, `INFISICAL_ENV`, `INFISICAL_ENV_SLUG` |
|
||||||
|
| `ClientId` | `INFISICAL_CLIENT_ID`, `INFISICAL_UNIVERSAL_AUTH_CLIENT_ID` |
|
||||||
|
| `ClientSecret` | `INFISICAL_CLIENT_SECRET`, `INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET` |
|
||||||
|
| `AccessToken` | `INFISICAL_TOKEN`, `INFISICAL_ACCESS_TOKEN`, `INFISICAL_AUTH_TOKEN` |
|
||||||
|
| `SecretPath` | `INFISICAL_SECRET_PATH`, `INFISICAL_DEFAULT_SECRET_PATH` |
|
||||||
|
| `ApiVersion` | `INFISICAL_API_VERSION` |
|
||||||
|
|
||||||
|
Sensitive values (`ClientSecret`, `AccessToken`) are read directly into a read-only `SecureString` and never logged.
|
||||||
|
|
||||||
|
### Zero-configuration example
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
[Environment]::SetEnvironmentVariable('INFISICAL_API_URL', 'https://app.infisical.com', 'User')
|
||||||
|
[Environment]::SetEnvironmentVariable('INFISICAL_ORG_ID', '00000000-0000-0000-0000-000000000000', 'User')
|
||||||
|
[Environment]::SetEnvironmentVariable('INFISICAL_PROJECT_ID', '11111111-1111-1111-1111-111111111111', 'User')
|
||||||
|
[Environment]::SetEnvironmentVariable('INFISICAL_ENVIRONMENT', 'dev', 'User')
|
||||||
|
[Environment]::SetEnvironmentVariable('INFISICAL_CLIENT_ID', 'machine-identity-client-id', 'User')
|
||||||
|
[Environment]::SetEnvironmentVariable('INFISICAL_CLIENT_SECRET', 'super-secret-value', 'User')
|
||||||
|
|
||||||
|
Connect-Infisical
|
||||||
|
Get-InfisicalSecrets
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mixed example (explicit values override discovery)
|
||||||
|
|
||||||
|
Explicit parameters always win over discovered values; blank/whitespace explicit values trigger discovery.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Connect-Infisical -Environment 'prod' # everything else discovered from environment
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
|
||||||
|
The resolver emits a single verbose line announcing the scan and one informational line per discovered variable (variable name and scope; values are never logged). Use `-Verbose` to see the scan announcement.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pwsh -NoProfile -ExecutionPolicy Bypass -File .\build.ps1 -RunTests
|
||||||
|
```
|
||||||
|
|
||||||
|
The script builds the binary, runs unit tests, publishes binaries into `Module/PSInfisicalAPI/bin/`, regenerates the manifest, and validates that the module imports.
|
||||||
|
|
||||||
|
## Continuous integration
|
||||||
|
|
||||||
|
`.gitea/workflows/publish-psgallery.yml` publishes the module to the PowerShell Gallery whenever a pull request is merged into `main`. The workflow expects a repository secret named `PSGALLERY_API_KEY` containing a valid Gallery API key.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Distributed under the GNU Affero General Public License v3.0. See [LICENSE](LICENSE).
|
||||||
|
|||||||
@@ -90,10 +90,10 @@ function Write-Manifest {
|
|||||||
RootModule = 'PSInfisicalAPI.psm1'
|
RootModule = 'PSInfisicalAPI.psm1'
|
||||||
ModuleVersion = '$ModuleVersion'
|
ModuleVersion = '$ModuleVersion'
|
||||||
GUID = '$ModuleGuid'
|
GUID = '$ModuleGuid'
|
||||||
Author = 'Alphaeus Mote'
|
Author = 'Grace Solutions'
|
||||||
CompanyName = ''
|
CompanyName = 'Grace Solutions'
|
||||||
Copyright = '(c) Alphaeus Mote. All rights reserved.'
|
Copyright = '(c) Grace Solutions. All rights reserved.'
|
||||||
Description = 'PSInfisicalAPI is a C# binary PowerShell module for the Infisical REST API.'
|
Description = 'PSInfisicalAPI is a C# binary PowerShell module for the Infisical REST API, providing cmdlets for authentication, secret retrieval, and export with automatic environment-variable discovery across Process, User, and Machine scopes.'
|
||||||
PowerShellVersion = '5.1'
|
PowerShellVersion = '5.1'
|
||||||
CompatiblePSEditions = @('Desktop','Core')
|
CompatiblePSEditions = @('Desktop','Core')
|
||||||
FunctionsToExport = @()
|
FunctionsToExport = @()
|
||||||
@@ -111,9 +111,10 @@ function Write-Manifest {
|
|||||||
TypesToProcess = @('PSInfisicalAPI.Types.ps1xml')
|
TypesToProcess = @('PSInfisicalAPI.Types.ps1xml')
|
||||||
PrivateData = @{
|
PrivateData = @{
|
||||||
PSData = @{
|
PSData = @{
|
||||||
Tags = @('Infisical','Secrets','API','SecureString')
|
Tags = @('Infisical','Secrets','API','SecureString','Vault','Authentication')
|
||||||
ProjectUri = ''
|
LicenseUri = 'https://www.gnu.org/licenses/agpl-3.0.html'
|
||||||
ReleaseNotes = ''
|
ProjectUri = 'https://prod.git.gracesolution.info/gsadmin/PSInfisicalAPI'
|
||||||
|
ReleaseNotes = 'See CHANGELOG.md in the project repository for release history.'
|
||||||
CommitHash = '$CommitHash'
|
CommitHash = '$CommitHash'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,7 +133,9 @@ function Update-Changelog {
|
|||||||
if ($existing -match [Regex]::Escape($marker)) { return }
|
if ($existing -match [Regex]::Escape($marker)) { return }
|
||||||
|
|
||||||
$insertion = "## $Version`r`n`r`n- Build produced from commit $CommitHash.`r`n`r`n"
|
$insertion = "## $Version`r`n`r`n- Build produced from commit $CommitHash.`r`n`r`n"
|
||||||
$updated = $existing -replace '## Unreleased', "## Unreleased`r`n`r`n$insertion## Unreleased (carried forward)"
|
$unreleasedRegex = [regex]::new('(?m)^## Unreleased\r?$')
|
||||||
|
if (-not $unreleasedRegex.IsMatch($existing)) { return }
|
||||||
|
$updated = $unreleasedRegex.Replace($existing, "## Unreleased`r`n`r`n$insertion## Unreleased (carried forward)", 1)
|
||||||
[System.IO.File]::WriteAllText($ChangelogFile.FullName, $updated, [System.Text.UTF8Encoding]::new($false))
|
[System.IO.File]::WriteAllText($ChangelogFile.FullName, $updated, [System.Text.UTF8Encoding]::new($false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,15 +151,33 @@ function Invoke-DotNet {
|
|||||||
|
|
||||||
function Test-ModuleImports {
|
function Test-ModuleImports {
|
||||||
param([System.IO.DirectoryInfo]$ModuleDirectory)
|
param([System.IO.DirectoryInfo]$ModuleDirectory)
|
||||||
Write-Step "Validating module import"
|
Write-Step "Validating module import, manifest, and help"
|
||||||
|
$manifestPath = [System.IO.Path]::Combine($ModuleDirectory.FullName, 'PSInfisicalAPI.psd1')
|
||||||
$script = @"
|
$script = @"
|
||||||
`$ErrorActionPreference = 'Stop'
|
`$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
`$manifest = Test-ModuleManifest -Path '$manifestPath'
|
||||||
|
if (`$null -eq `$manifest) {
|
||||||
|
throw "Test-ModuleManifest returned no result for '$manifestPath'."
|
||||||
|
}
|
||||||
|
|
||||||
Import-Module -Name '$($ModuleDirectory.FullName)' -Force
|
Import-Module -Name '$($ModuleDirectory.FullName)' -Force
|
||||||
|
|
||||||
`$cmds = @('Connect-Infisical','Disconnect-Infisical','Get-InfisicalSecrets','Get-InfisicalSecret','ConvertTo-InfisicalSecretDictionary','Export-InfisicalSecrets')
|
`$cmds = @('Connect-Infisical','Disconnect-Infisical','Get-InfisicalSecrets','Get-InfisicalSecret','ConvertTo-InfisicalSecretDictionary','Export-InfisicalSecrets')
|
||||||
foreach (`$c in `$cmds) {
|
foreach (`$c in `$cmds) {
|
||||||
if (-not (Get-Command -Name `$c -Module PSInfisicalAPI -ErrorAction SilentlyContinue)) {
|
if (-not (Get-Command -Name `$c -Module PSInfisicalAPI -ErrorAction SilentlyContinue)) {
|
||||||
throw "Cmdlet not found: `$c"
|
throw "Cmdlet not found: `$c"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
`$help = Get-Help -Name `$c -ErrorAction SilentlyContinue
|
||||||
|
if (`$null -eq `$help) {
|
||||||
|
throw "Get-Help returned nothing for cmdlet: `$c"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
`$about = Get-Help -Name 'about_PSInfisicalAPI' -ErrorAction SilentlyContinue
|
||||||
|
if (`$null -eq `$about -or [string]::IsNullOrWhiteSpace((`$about | Out-String))) {
|
||||||
|
throw "Get-Help 'about_PSInfisicalAPI' returned no content. Ensure en-US/about_PSInfisicalAPI.help.txt is present."
|
||||||
}
|
}
|
||||||
"@
|
"@
|
||||||
|
|
||||||
@@ -251,11 +272,7 @@ Write-Manifest -Path $manifestPath -ModuleVersion $buildVersion -CommitHash $com
|
|||||||
|
|
||||||
Update-Changelog -Version $buildVersion -CommitHash $commitHash
|
Update-Changelog -Version $buildVersion -CommitHash $commitHash
|
||||||
|
|
||||||
try {
|
|
||||||
Test-ModuleImports -ModuleDirectory $ModuleRoot
|
Test-ModuleImports -ModuleDirectory $ModuleRoot
|
||||||
} catch {
|
|
||||||
Write-Warning "Module import validation reported: $($_.Exception.Message)"
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($CreateRelease.IsPresent) {
|
if ($CreateRelease.IsPresent) {
|
||||||
$releaseDir = [System.IO.DirectoryInfo][System.IO.Path]::Combine($ReleasesDir.FullName, $buildVersion)
|
$releaseDir = [System.IO.DirectoryInfo][System.IO.Path]::Combine($ReleasesDir.FullName, $buildVersion)
|
||||||
|
|||||||
+11
-11
@@ -187,7 +187,7 @@ Example shape:
|
|||||||
RootModule = 'PSInfisicalAPI.psm1'
|
RootModule = 'PSInfisicalAPI.psm1'
|
||||||
ModuleVersion = 'yyyy.MM.dd.HHmm'
|
ModuleVersion = 'yyyy.MM.dd.HHmm'
|
||||||
GUID = '<stable-guid>'
|
GUID = '<stable-guid>'
|
||||||
Author = 'Alphaeus Mote'
|
Author = 'Grace Solutions'
|
||||||
CompanyName = ''
|
CompanyName = ''
|
||||||
Copyright = ''
|
Copyright = ''
|
||||||
PowerShellVersion = '5.1'
|
PowerShellVersion = '5.1'
|
||||||
@@ -954,10 +954,10 @@ Get-InfisicalSecrets `
|
|||||||
[-Environment <string>] `
|
[-Environment <string>] `
|
||||||
[-SecretPath <string>] `
|
[-SecretPath <string>] `
|
||||||
[-Recursive] `
|
[-Recursive] `
|
||||||
[-IncludeImports <bool>] `
|
[-IncludeImports] `
|
||||||
[-IncludePersonalOverrides] `
|
[-IncludePersonalOverrides] `
|
||||||
[-ExpandSecretReferences <bool>] `
|
[-ExpandSecretReferences] `
|
||||||
[-ViewSecretValue <bool>] `
|
[-ViewSecretValue] `
|
||||||
[-MetadataFilter <hashtable>] `
|
[-MetadataFilter <hashtable>] `
|
||||||
[-TagSlugs <string[]>]
|
[-TagSlugs <string[]>]
|
||||||
```
|
```
|
||||||
@@ -969,8 +969,8 @@ ProjectId: Current connection ProjectId
|
|||||||
Environment: Current connection Environment
|
Environment: Current connection Environment
|
||||||
SecretPath: Current connection DefaultSecretPath or /
|
SecretPath: Current connection DefaultSecretPath or /
|
||||||
Recursive: false
|
Recursive: false
|
||||||
IncludeImports: true
|
IncludeImports: false
|
||||||
ExpandSecretReferences: true
|
ExpandSecretReferences: false
|
||||||
ViewSecretValue: true
|
ViewSecretValue: true
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1020,9 +1020,9 @@ Get-InfisicalSecret `
|
|||||||
[-SecretPath <string>] `
|
[-SecretPath <string>] `
|
||||||
[-Version <int>] `
|
[-Version <int>] `
|
||||||
[-Type <InfisicalSecretType>] `
|
[-Type <InfisicalSecretType>] `
|
||||||
[-ViewSecretValue <bool>] `
|
[-ViewSecretValue] `
|
||||||
[-ExpandSecretReferences <bool>] `
|
[-ExpandSecretReferences] `
|
||||||
[-IncludeImports <bool>]
|
[-IncludeImports]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Parameter Attributes
|
## Parameter Attributes
|
||||||
@@ -1041,8 +1041,8 @@ Environment: Current connection Environment
|
|||||||
SecretPath: Current connection DefaultSecretPath or /
|
SecretPath: Current connection DefaultSecretPath or /
|
||||||
Type: Shared
|
Type: Shared
|
||||||
ViewSecretValue: true
|
ViewSecretValue: true
|
||||||
ExpandSecretReferences: true
|
ExpandSecretReferences: false
|
||||||
IncludeImports: true
|
IncludeImports: false
|
||||||
```
|
```
|
||||||
|
|
||||||
## Behavior
|
## Behavior
|
||||||
|
|||||||
@@ -89,10 +89,13 @@ namespace PSInfisicalAPI.Cmdlets
|
|||||||
throw new InfisicalAuthenticationException("Authentication did not produce an access token.");
|
throw new InfisicalAuthenticationException("Authentication did not produce an access token.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool apiVersionExplicitlyBound = MyInvocation.BoundParameters.ContainsKey("ApiVersion");
|
||||||
|
|
||||||
InfisicalConnection connection = new InfisicalConnection
|
InfisicalConnection connection = new InfisicalConnection
|
||||||
{
|
{
|
||||||
BaseUri = BaseUri,
|
BaseUri = BaseUri,
|
||||||
ApiVersion = ApiVersion,
|
ApiVersion = ApiVersion,
|
||||||
|
PinnedApiVersion = apiVersionExplicitlyBound ? ApiVersion : null,
|
||||||
AuthType = authType,
|
AuthType = authType,
|
||||||
OrganizationId = OrganizationId,
|
OrganizationId = OrganizationId,
|
||||||
ProjectId = ProjectId,
|
ProjectId = ProjectId,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ namespace PSInfisicalAPI.Cmdlets
|
|||||||
{
|
{
|
||||||
[Cmdlet(VerbsData.ConvertTo, "InfisicalSecretDictionary")]
|
[Cmdlet(VerbsData.ConvertTo, "InfisicalSecretDictionary")]
|
||||||
[OutputType(typeof(Dictionary<string, SecureString>))]
|
[OutputType(typeof(Dictionary<string, SecureString>))]
|
||||||
|
[OutputType(typeof(Dictionary<string, string>))]
|
||||||
public sealed class ConvertToInfisicalSecretDictionaryCmdlet : InfisicalCmdletBase
|
public sealed class ConvertToInfisicalSecretDictionaryCmdlet : InfisicalCmdletBase
|
||||||
{
|
{
|
||||||
[Parameter(Mandatory = true, ValueFromPipeline = true)]
|
[Parameter(Mandatory = true, ValueFromPipeline = true)]
|
||||||
@@ -17,6 +18,9 @@ namespace PSInfisicalAPI.Cmdlets
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public InfisicalDuplicateKeyBehavior DuplicateKeyBehavior { get; set; } = InfisicalDuplicateKeyBehavior.Error;
|
public InfisicalDuplicateKeyBehavior DuplicateKeyBehavior { get; set; } = InfisicalDuplicateKeyBehavior.Error;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public SwitchParameter AsPlainText { get; set; }
|
||||||
|
|
||||||
private readonly List<InfisicalSecret> _buffer = new List<InfisicalSecret>();
|
private readonly List<InfisicalSecret> _buffer = new List<InfisicalSecret>();
|
||||||
|
|
||||||
protected override void ProcessRecord()
|
protected override void ProcessRecord()
|
||||||
@@ -36,7 +40,26 @@ namespace PSInfisicalAPI.Cmdlets
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Dictionary<string, SecureString> dictionary = new Dictionary<string, SecureString>(StringComparer.OrdinalIgnoreCase);
|
if (AsPlainText.IsPresent)
|
||||||
|
{
|
||||||
|
Dictionary<string, string> plain = BuildDictionary<string>(secret => secret.GetPlainTextValue());
|
||||||
|
WriteObject(plain);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Dictionary<string, SecureString> secure = BuildDictionary<SecureString>(secret => secret.SecretValue);
|
||||||
|
WriteObject(secure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
ThrowTerminatingForException("ConvertToInfisicalSecretDictionaryCmdlet", "ConvertToDictionary", exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<string, TValue> BuildDictionary<TValue>(Func<InfisicalSecret, TValue> valueSelector)
|
||||||
|
{
|
||||||
|
Dictionary<string, TValue> dictionary = new Dictionary<string, TValue>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
foreach (InfisicalSecret secret in _buffer)
|
foreach (InfisicalSecret secret in _buffer)
|
||||||
{
|
{
|
||||||
@@ -51,21 +74,16 @@ namespace PSInfisicalAPI.Cmdlets
|
|||||||
|
|
||||||
if (DuplicateKeyBehavior == InfisicalDuplicateKeyBehavior.LastWins)
|
if (DuplicateKeyBehavior == InfisicalDuplicateKeyBehavior.LastWins)
|
||||||
{
|
{
|
||||||
dictionary[key] = secret.SecretValue;
|
dictionary[key] = valueSelector(secret);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
dictionary[key] = secret.SecretValue;
|
dictionary[key] = valueSelector(secret);
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteObject(dictionary);
|
return dictionary;
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
ThrowTerminatingForException("ConvertToInfisicalSecretDictionaryCmdlet", "ConvertToDictionary", exception);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ namespace PSInfisicalAPI.Cmdlets
|
|||||||
{
|
{
|
||||||
switch (encoding)
|
switch (encoding)
|
||||||
{
|
{
|
||||||
|
case InfisicalExportEncoding.UTF8: return new UTF8Encoding(false);
|
||||||
case InfisicalExportEncoding.UTF8Bom: return new UTF8Encoding(true);
|
case InfisicalExportEncoding.UTF8Bom: return new UTF8Encoding(true);
|
||||||
case InfisicalExportEncoding.Unicode: return new UnicodeEncoding();
|
case InfisicalExportEncoding.Unicode: return new UnicodeEncoding();
|
||||||
default: return new UTF8Encoding(false);
|
default: return new UTF8Encoding(false);
|
||||||
|
|||||||
@@ -16,11 +16,12 @@ namespace PSInfisicalAPI.Cmdlets
|
|||||||
[Parameter] public string ProjectId { get; set; }
|
[Parameter] public string ProjectId { get; set; }
|
||||||
[Parameter] public string Environment { get; set; }
|
[Parameter] public string Environment { get; set; }
|
||||||
[Parameter] public string SecretPath { get; set; }
|
[Parameter] public string SecretPath { get; set; }
|
||||||
|
[Parameter] public string ApiVersion { get; set; }
|
||||||
[Parameter] public int? Version { get; set; }
|
[Parameter] public int? Version { get; set; }
|
||||||
[Parameter] public InfisicalSecretType Type { get; set; } = InfisicalSecretType.Shared;
|
[Parameter] public InfisicalSecretType Type { get; set; } = InfisicalSecretType.Shared;
|
||||||
[Parameter] public bool ViewSecretValue { get; set; } = true;
|
[Parameter] public SwitchParameter ViewSecretValue { get; set; } = SwitchParameter.Present;
|
||||||
[Parameter] public bool ExpandSecretReferences { get; set; } = true;
|
[Parameter] public SwitchParameter ExpandSecretReferences { get; set; }
|
||||||
[Parameter] public bool IncludeImports { get; set; } = true;
|
[Parameter] public SwitchParameter IncludeImports { get; set; }
|
||||||
|
|
||||||
protected override void ProcessRecord()
|
protected override void ProcessRecord()
|
||||||
{
|
{
|
||||||
@@ -34,11 +35,12 @@ namespace PSInfisicalAPI.Cmdlets
|
|||||||
ProjectId = ProjectId,
|
ProjectId = ProjectId,
|
||||||
Environment = Environment,
|
Environment = Environment,
|
||||||
SecretPath = SecretPath,
|
SecretPath = SecretPath,
|
||||||
|
ApiVersion = ApiVersion,
|
||||||
Version = Version,
|
Version = Version,
|
||||||
Type = Type.ToString(),
|
Type = Type.ToString(),
|
||||||
ViewSecretValue = ViewSecretValue,
|
ViewSecretValue = ViewSecretValue.IsPresent,
|
||||||
ExpandSecretReferences = ExpandSecretReferences,
|
ExpandSecretReferences = ExpandSecretReferences.IsPresent,
|
||||||
IncludeImports = IncludeImports
|
IncludeImports = IncludeImports.IsPresent
|
||||||
};
|
};
|
||||||
|
|
||||||
InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger);
|
InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger);
|
||||||
|
|||||||
@@ -15,11 +15,12 @@ namespace PSInfisicalAPI.Cmdlets
|
|||||||
[Parameter] public string ProjectId { get; set; }
|
[Parameter] public string ProjectId { get; set; }
|
||||||
[Parameter] public string Environment { get; set; }
|
[Parameter] public string Environment { get; set; }
|
||||||
[Parameter] public string SecretPath { get; set; }
|
[Parameter] public string SecretPath { get; set; }
|
||||||
|
[Parameter] public string ApiVersion { get; set; }
|
||||||
[Parameter] public SwitchParameter Recursive { get; set; }
|
[Parameter] public SwitchParameter Recursive { get; set; }
|
||||||
[Parameter] public bool IncludeImports { get; set; } = true;
|
[Parameter] public SwitchParameter IncludeImports { get; set; }
|
||||||
[Parameter] public SwitchParameter IncludePersonalOverrides { get; set; }
|
[Parameter] public SwitchParameter IncludePersonalOverrides { get; set; }
|
||||||
[Parameter] public bool ExpandSecretReferences { get; set; } = true;
|
[Parameter] public SwitchParameter ExpandSecretReferences { get; set; }
|
||||||
[Parameter] public bool ViewSecretValue { get; set; } = true;
|
[Parameter] public SwitchParameter ViewSecretValue { get; set; } = SwitchParameter.Present;
|
||||||
[Parameter] public Hashtable MetadataFilter { get; set; }
|
[Parameter] public Hashtable MetadataFilter { get; set; }
|
||||||
[Parameter] public string[] TagSlugs { get; set; }
|
[Parameter] public string[] TagSlugs { get; set; }
|
||||||
|
|
||||||
@@ -34,11 +35,12 @@ namespace PSInfisicalAPI.Cmdlets
|
|||||||
ProjectId = ProjectId,
|
ProjectId = ProjectId,
|
||||||
Environment = Environment,
|
Environment = Environment,
|
||||||
SecretPath = SecretPath,
|
SecretPath = SecretPath,
|
||||||
|
ApiVersion = ApiVersion,
|
||||||
Recursive = Recursive.IsPresent,
|
Recursive = Recursive.IsPresent,
|
||||||
IncludeImports = IncludeImports,
|
IncludeImports = IncludeImports.IsPresent,
|
||||||
IncludePersonalOverrides = IncludePersonalOverrides.IsPresent,
|
IncludePersonalOverrides = IncludePersonalOverrides.IsPresent,
|
||||||
ExpandSecretReferences = ExpandSecretReferences,
|
ExpandSecretReferences = ExpandSecretReferences.IsPresent,
|
||||||
ViewSecretValue = ViewSecretValue,
|
ViewSecretValue = ViewSecretValue.IsPresent,
|
||||||
MetadataFilter = ToStringDictionary(MetadataFilter),
|
MetadataFilter = ToStringDictionary(MetadataFilter),
|
||||||
TagSlugs = TagSlugs
|
TagSlugs = TagSlugs
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Security;
|
using System.Security;
|
||||||
using PSInfisicalAPI.Models;
|
using PSInfisicalAPI.Models;
|
||||||
|
|
||||||
@@ -8,6 +9,7 @@ namespace PSInfisicalAPI.Connections
|
|||||||
{
|
{
|
||||||
public Uri BaseUri { get; set; }
|
public Uri BaseUri { get; set; }
|
||||||
public string ApiVersion { get; set; }
|
public string ApiVersion { get; set; }
|
||||||
|
public string PinnedApiVersion { get; set; }
|
||||||
public InfisicalAuthType AuthType { get; set; }
|
public InfisicalAuthType AuthType { get; set; }
|
||||||
public string OrganizationId { get; set; }
|
public string OrganizationId { get; set; }
|
||||||
public string ProjectId { get; set; }
|
public string ProjectId { get; set; }
|
||||||
@@ -17,6 +19,8 @@ namespace PSInfisicalAPI.Connections
|
|||||||
public DateTimeOffset? ExpiresAtUtc { get; set; }
|
public DateTimeOffset? ExpiresAtUtc { get; set; }
|
||||||
public bool IsConnected { get; set; }
|
public bool IsConnected { get; set; }
|
||||||
|
|
||||||
|
public Dictionary<string, string> ResolvedEndpointVersions { get; } = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||||
|
|
||||||
internal SecureString AccessToken { get; set; }
|
internal SecureString AccessToken { get; set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ namespace PSInfisicalAPI.Endpoints
|
|||||||
{
|
{
|
||||||
public static class InfisicalEndpointRegistry
|
public static class InfisicalEndpointRegistry
|
||||||
{
|
{
|
||||||
private static readonly Dictionary<string, InfisicalEndpointDefinition> Definitions =
|
private static readonly Dictionary<string, List<InfisicalEndpointDefinition>> Candidates =
|
||||||
new Dictionary<string, InfisicalEndpointDefinition>
|
new Dictionary<string, List<InfisicalEndpointDefinition>>
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
InfisicalEndpointNames.UniversalAuthLogin,
|
InfisicalEndpointNames.UniversalAuthLogin,
|
||||||
|
new List<InfisicalEndpointDefinition>
|
||||||
|
{
|
||||||
new InfisicalEndpointDefinition
|
new InfisicalEndpointDefinition
|
||||||
{
|
{
|
||||||
Name = InfisicalEndpointNames.UniversalAuthLogin,
|
Name = InfisicalEndpointNames.UniversalAuthLogin,
|
||||||
@@ -21,9 +23,12 @@ namespace PSInfisicalAPI.Endpoints
|
|||||||
ContainsSecretMaterialInRequest = true,
|
ContainsSecretMaterialInRequest = true,
|
||||||
ContainsSecretMaterialInResponse = true
|
ContainsSecretMaterialInResponse = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
InfisicalEndpointNames.ListSecrets,
|
InfisicalEndpointNames.ListSecrets,
|
||||||
|
new List<InfisicalEndpointDefinition>
|
||||||
|
{
|
||||||
new InfisicalEndpointDefinition
|
new InfisicalEndpointDefinition
|
||||||
{
|
{
|
||||||
Name = InfisicalEndpointNames.ListSecrets,
|
Name = InfisicalEndpointNames.ListSecrets,
|
||||||
@@ -34,10 +39,24 @@ namespace PSInfisicalAPI.Endpoints
|
|||||||
RequiresAuthorization = true,
|
RequiresAuthorization = true,
|
||||||
ContainsSecretMaterialInRequest = false,
|
ContainsSecretMaterialInRequest = false,
|
||||||
ContainsSecretMaterialInResponse = true
|
ContainsSecretMaterialInResponse = true
|
||||||
|
},
|
||||||
|
new InfisicalEndpointDefinition
|
||||||
|
{
|
||||||
|
Name = InfisicalEndpointNames.ListSecrets,
|
||||||
|
Resource = "Secrets",
|
||||||
|
Version = "v3",
|
||||||
|
Method = "GET",
|
||||||
|
Template = "/api/v3/secrets/raw",
|
||||||
|
RequiresAuthorization = true,
|
||||||
|
ContainsSecretMaterialInRequest = false,
|
||||||
|
ContainsSecretMaterialInResponse = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
InfisicalEndpointNames.RetrieveSecret,
|
InfisicalEndpointNames.RetrieveSecret,
|
||||||
|
new List<InfisicalEndpointDefinition>
|
||||||
|
{
|
||||||
new InfisicalEndpointDefinition
|
new InfisicalEndpointDefinition
|
||||||
{
|
{
|
||||||
Name = InfisicalEndpointNames.RetrieveSecret,
|
Name = InfisicalEndpointNames.RetrieveSecret,
|
||||||
@@ -48,24 +67,26 @@ namespace PSInfisicalAPI.Endpoints
|
|||||||
RequiresAuthorization = true,
|
RequiresAuthorization = true,
|
||||||
ContainsSecretMaterialInRequest = false,
|
ContainsSecretMaterialInRequest = false,
|
||||||
ContainsSecretMaterialInResponse = true
|
ContainsSecretMaterialInResponse = true
|
||||||
|
},
|
||||||
|
new InfisicalEndpointDefinition
|
||||||
|
{
|
||||||
|
Name = InfisicalEndpointNames.RetrieveSecret,
|
||||||
|
Resource = "Secrets",
|
||||||
|
Version = "v3",
|
||||||
|
Method = "GET",
|
||||||
|
Template = "/api/v3/secrets/raw/{secretName}",
|
||||||
|
RequiresAuthorization = true,
|
||||||
|
ContainsSecretMaterialInRequest = false,
|
||||||
|
ContainsSecretMaterialInResponse = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public static InfisicalEndpointDefinition Get(string name)
|
public static InfisicalEndpointDefinition Get(string name)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(name))
|
List<InfisicalEndpointDefinition> list = GetCandidatesInternal(name);
|
||||||
{
|
return list[0];
|
||||||
throw new InfisicalConfigurationException("Endpoint name must be provided.");
|
|
||||||
}
|
|
||||||
|
|
||||||
InfisicalEndpointDefinition definition;
|
|
||||||
if (!Definitions.TryGetValue(name, out definition))
|
|
||||||
{
|
|
||||||
throw new InfisicalConfigurationException(string.Concat("Unknown endpoint name: ", name));
|
|
||||||
}
|
|
||||||
|
|
||||||
return definition;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryGet(string name, out InfisicalEndpointDefinition definition)
|
public static bool TryGet(string name, out InfisicalEndpointDefinition definition)
|
||||||
@@ -76,12 +97,50 @@ namespace PSInfisicalAPI.Endpoints
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Definitions.TryGetValue(name, out definition);
|
List<InfisicalEndpointDefinition> list;
|
||||||
|
if (!Candidates.TryGetValue(name, out list) || list == null || list.Count == 0)
|
||||||
|
{
|
||||||
|
definition = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
definition = list[0];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyList<InfisicalEndpointDefinition> GetCandidates(string name)
|
||||||
|
{
|
||||||
|
return GetCandidatesInternal(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<InfisicalEndpointDefinition> All()
|
public static IEnumerable<InfisicalEndpointDefinition> All()
|
||||||
{
|
{
|
||||||
return Definitions.Values;
|
List<InfisicalEndpointDefinition> result = new List<InfisicalEndpointDefinition>();
|
||||||
|
foreach (List<InfisicalEndpointDefinition> list in Candidates.Values)
|
||||||
|
{
|
||||||
|
foreach (InfisicalEndpointDefinition definition in list)
|
||||||
|
{
|
||||||
|
result.Add(definition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<InfisicalEndpointDefinition> GetCandidatesInternal(string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
|
throw new InfisicalConfigurationException("Endpoint name must be provided.");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<InfisicalEndpointDefinition> list;
|
||||||
|
if (!Candidates.TryGetValue(name, out list) || list == null || list.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InfisicalConfigurationException(string.Concat("Unknown endpoint name: ", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,14 @@ namespace PSInfisicalAPI.Http
|
|||||||
webRequest.UserAgent = "PSInfisicalAPI";
|
webRequest.UserAgent = "PSInfisicalAPI";
|
||||||
webRequest.Timeout = _timeoutSeconds * 1000;
|
webRequest.Timeout = _timeoutSeconds * 1000;
|
||||||
webRequest.ReadWriteTimeout = _timeoutSeconds * 1000;
|
webRequest.ReadWriteTimeout = _timeoutSeconds * 1000;
|
||||||
|
webRequest.UseDefaultCredentials = true;
|
||||||
|
|
||||||
|
IWebProxy systemProxy = WebRequest.GetSystemWebProxy();
|
||||||
|
if (systemProxy != null)
|
||||||
|
{
|
||||||
|
systemProxy.Credentials = CredentialCache.DefaultNetworkCredentials;
|
||||||
|
webRequest.Proxy = systemProxy;
|
||||||
|
}
|
||||||
|
|
||||||
ApplyHeaders(webRequest, request.Headers);
|
ApplyHeaders(webRequest, request.Headers);
|
||||||
|
|
||||||
|
|||||||
@@ -39,13 +39,7 @@ namespace PSInfisicalAPI.Logging
|
|||||||
public void Error(string component, string message)
|
public void Error(string component, string message)
|
||||||
{
|
{
|
||||||
string line = InfisicalLogFormatter.FormatNow(InfisicalLogLevel.Error, component, message);
|
string line = InfisicalLogFormatter.FormatNow(InfisicalLogLevel.Error, component, message);
|
||||||
ErrorRecord record = new ErrorRecord(
|
_cmdlet.WriteWarning(line);
|
||||||
new InvalidOperationException(message ?? string.Empty),
|
|
||||||
"PSInfisicalAPI.Error",
|
|
||||||
ErrorCategory.NotSpecified,
|
|
||||||
component);
|
|
||||||
record.ErrorDetails = new ErrorDetails(line);
|
|
||||||
_cmdlet.WriteError(record);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ namespace PSInfisicalAPI.Models
|
|||||||
SecureStringUtility.UsePlainText(SecretValue, action);
|
SecureStringUtility.UsePlainText(SecretValue, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetPlainTextValue()
|
||||||
|
{
|
||||||
|
if (SecretValue == null) { return null; }
|
||||||
|
return SecureStringUtility.UsePlainText(SecretValue, plainText => plainText);
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return SecretName;
|
return SecretName;
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
<AssemblyVersion>$(BuildAssemblyVersion)</AssemblyVersion>
|
<AssemblyVersion>$(BuildAssemblyVersion)</AssemblyVersion>
|
||||||
<FileVersion>$(BuildAssemblyVersion)</FileVersion>
|
<FileVersion>$(BuildAssemblyVersion)</FileVersion>
|
||||||
<InformationalVersion>$(BuildVersion)</InformationalVersion>
|
<InformationalVersion>$(BuildVersion)</InformationalVersion>
|
||||||
<Company />
|
<Company>Grace Solutions</Company>
|
||||||
<Authors>Alphaeus Mote</Authors>
|
<Authors>Grace Solutions</Authors>
|
||||||
<Product>PSInfisicalAPI</Product>
|
<Product>PSInfisicalAPI</Product>
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ namespace PSInfisicalAPI.Secrets
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool hidden = dto.SecretValueHidden || IsHiddenPlaceholder(dto.SecretValue);
|
||||||
|
|
||||||
InfisicalSecret secret = new InfisicalSecret
|
InfisicalSecret secret = new InfisicalSecret
|
||||||
{
|
{
|
||||||
Id = dto.Id,
|
Id = dto.Id,
|
||||||
@@ -24,8 +26,8 @@ namespace PSInfisicalAPI.Secrets
|
|||||||
Version = dto.Version,
|
Version = dto.Version,
|
||||||
Type = ParseType(dto.Type),
|
Type = ParseType(dto.Type),
|
||||||
SecretName = dto.SecretKey,
|
SecretName = dto.SecretKey,
|
||||||
SecretValue = SecureStringUtility.ToReadOnlySecureString(dto.SecretValue),
|
SecretValue = hidden ? null : SecureStringUtility.ToReadOnlySecureString(dto.SecretValue),
|
||||||
SecretValueHidden = dto.SecretValueHidden,
|
SecretValueHidden = hidden,
|
||||||
SecretPath = dto.SecretPath,
|
SecretPath = dto.SecretPath,
|
||||||
SecretComment = dto.SecretComment,
|
SecretComment = dto.SecretComment,
|
||||||
CreatedAtUtc = ParseTimestamp(dto.CreatedAt),
|
CreatedAtUtc = ParseTimestamp(dto.CreatedAt),
|
||||||
@@ -41,6 +43,11 @@ namespace PSInfisicalAPI.Secrets
|
|||||||
return secret;
|
return secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsHiddenPlaceholder(string value)
|
||||||
|
{
|
||||||
|
return string.Equals(value, "<hidden-by-infisical>", StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
public static InfisicalSecret[] MapMany(IEnumerable<InfisicalSecretResponseDto> items)
|
public static InfisicalSecret[] MapMany(IEnumerable<InfisicalSecretResponseDto> items)
|
||||||
{
|
{
|
||||||
if (items == null)
|
if (items == null)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace PSInfisicalAPI.Secrets
|
|||||||
public string ProjectId { get; set; }
|
public string ProjectId { get; set; }
|
||||||
public string Environment { get; set; }
|
public string Environment { get; set; }
|
||||||
public string SecretPath { get; set; }
|
public string SecretPath { get; set; }
|
||||||
|
public string ApiVersion { get; set; }
|
||||||
public bool Recursive { get; set; }
|
public bool Recursive { get; set; }
|
||||||
public bool? IncludeImports { get; set; }
|
public bool? IncludeImports { get; set; }
|
||||||
public bool IncludePersonalOverrides { get; set; }
|
public bool IncludePersonalOverrides { get; set; }
|
||||||
@@ -22,6 +23,7 @@ namespace PSInfisicalAPI.Secrets
|
|||||||
public string ProjectId { get; set; }
|
public string ProjectId { get; set; }
|
||||||
public string Environment { get; set; }
|
public string Environment { get; set; }
|
||||||
public string SecretPath { get; set; }
|
public string SecretPath { get; set; }
|
||||||
|
public string ApiVersion { get; set; }
|
||||||
public int? Version { get; set; }
|
public int? Version { get; set; }
|
||||||
public string Type { get; set; }
|
public string Type { get; set; }
|
||||||
public bool? ViewSecretValue { get; set; }
|
public bool? ViewSecretValue { get; set; }
|
||||||
|
|||||||
@@ -32,14 +32,15 @@ namespace PSInfisicalAPI.Secrets
|
|||||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||||
if (query == null) { throw new ArgumentNullException(nameof(query)); }
|
if (query == null) { throw new ArgumentNullException(nameof(query)); }
|
||||||
|
|
||||||
InfisicalEndpointDefinition definition = InfisicalEndpointRegistry.Get(InfisicalEndpointNames.ListSecrets);
|
string resolvedProjectId = FirstNonEmpty(query.ProjectId, connection.ProjectId);
|
||||||
|
|
||||||
List<KeyValuePair<string, string>> queryParameters = new List<KeyValuePair<string, string>>();
|
List<KeyValuePair<string, string>> queryParameters = new List<KeyValuePair<string, string>>();
|
||||||
AddIfNotNull(queryParameters, "workspaceId", FirstNonEmpty(query.ProjectId, connection.ProjectId));
|
AddIfNotNull(queryParameters, "workspaceId", resolvedProjectId);
|
||||||
|
AddIfNotNull(queryParameters, "projectId", resolvedProjectId);
|
||||||
AddIfNotNull(queryParameters, "environment", FirstNonEmpty(query.Environment, connection.Environment));
|
AddIfNotNull(queryParameters, "environment", FirstNonEmpty(query.Environment, connection.Environment));
|
||||||
AddIfNotNull(queryParameters, "secretPath", FirstNonEmpty(query.SecretPath, connection.DefaultSecretPath, "/"));
|
AddIfNotNull(queryParameters, "secretPath", FirstNonEmpty(query.SecretPath, connection.DefaultSecretPath, "/"));
|
||||||
queryParameters.Add(new KeyValuePair<string, string>("recursive", query.Recursive ? "true" : "false"));
|
queryParameters.Add(new KeyValuePair<string, string>("recursive", query.Recursive ? "true" : "false"));
|
||||||
if (query.IncludeImports.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("include_imports", query.IncludeImports.Value ? "true" : "false")); }
|
if (query.IncludeImports.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("includeImports", query.IncludeImports.Value ? "true" : "false")); }
|
||||||
if (query.IncludePersonalOverrides) { queryParameters.Add(new KeyValuePair<string, string>("includePersonalOverrides", "true")); }
|
if (query.IncludePersonalOverrides) { queryParameters.Add(new KeyValuePair<string, string>("includePersonalOverrides", "true")); }
|
||||||
if (query.ExpandSecretReferences.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("expandSecretReferences", query.ExpandSecretReferences.Value ? "true" : "false")); }
|
if (query.ExpandSecretReferences.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("expandSecretReferences", query.ExpandSecretReferences.Value ? "true" : "false")); }
|
||||||
if (query.ViewSecretValue.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("viewSecretValue", query.ViewSecretValue.Value ? "true" : "false")); }
|
if (query.ViewSecretValue.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("viewSecretValue", query.ViewSecretValue.Value ? "true" : "false")); }
|
||||||
@@ -60,13 +61,18 @@ namespace PSInfisicalAPI.Secrets
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Uri uri = InfisicalUriBuilder.Build(connection.BaseUri, definition, null, queryParameters);
|
|
||||||
InfisicalHttpResponse response = ExecuteAuthorized(connection, definition, "RetrieveSecrets", uri, null);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.Information(Component, "Attempting to retrieve Infisical secrets. Please Wait...");
|
_logger.Information(Component, "Attempting to retrieve Infisical secrets. Please Wait...");
|
||||||
EnsureSuccess(response, definition);
|
|
||||||
|
InfisicalHttpResponse response = SendWithVersionFallback(
|
||||||
|
connection,
|
||||||
|
InfisicalEndpointNames.ListSecrets,
|
||||||
|
query.ApiVersion,
|
||||||
|
"RetrieveSecrets",
|
||||||
|
null,
|
||||||
|
queryParameters,
|
||||||
|
null);
|
||||||
|
|
||||||
InfisicalSecretListResponseDto dto = _serializer.Deserialize<InfisicalSecretListResponseDto>(response.Body);
|
InfisicalSecretListResponseDto dto = _serializer.Deserialize<InfisicalSecretListResponseDto>(response.Body);
|
||||||
response.Clear();
|
response.Clear();
|
||||||
@@ -88,27 +94,33 @@ namespace PSInfisicalAPI.Secrets
|
|||||||
if (query == null) { throw new ArgumentNullException(nameof(query)); }
|
if (query == null) { throw new ArgumentNullException(nameof(query)); }
|
||||||
if (string.IsNullOrEmpty(query.SecretName)) { throw new InfisicalConfigurationException("SecretName is required."); }
|
if (string.IsNullOrEmpty(query.SecretName)) { throw new InfisicalConfigurationException("SecretName is required."); }
|
||||||
|
|
||||||
InfisicalEndpointDefinition definition = InfisicalEndpointRegistry.Get(InfisicalEndpointNames.RetrieveSecret);
|
|
||||||
|
|
||||||
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "secretName", query.SecretName } };
|
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "secretName", query.SecretName } };
|
||||||
|
|
||||||
|
string resolvedProjectId = FirstNonEmpty(query.ProjectId, connection.ProjectId);
|
||||||
|
|
||||||
List<KeyValuePair<string, string>> queryParameters = new List<KeyValuePair<string, string>>();
|
List<KeyValuePair<string, string>> queryParameters = new List<KeyValuePair<string, string>>();
|
||||||
AddIfNotNull(queryParameters, "workspaceId", FirstNonEmpty(query.ProjectId, connection.ProjectId));
|
AddIfNotNull(queryParameters, "workspaceId", resolvedProjectId);
|
||||||
|
AddIfNotNull(queryParameters, "projectId", resolvedProjectId);
|
||||||
AddIfNotNull(queryParameters, "environment", FirstNonEmpty(query.Environment, connection.Environment));
|
AddIfNotNull(queryParameters, "environment", FirstNonEmpty(query.Environment, connection.Environment));
|
||||||
AddIfNotNull(queryParameters, "secretPath", FirstNonEmpty(query.SecretPath, connection.DefaultSecretPath, "/"));
|
AddIfNotNull(queryParameters, "secretPath", FirstNonEmpty(query.SecretPath, connection.DefaultSecretPath, "/"));
|
||||||
AddIfNotNull(queryParameters, "type", string.IsNullOrEmpty(query.Type) ? "shared" : query.Type.ToLowerInvariant());
|
AddIfNotNull(queryParameters, "type", string.IsNullOrEmpty(query.Type) ? "shared" : query.Type.ToLowerInvariant());
|
||||||
if (query.Version.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("version", query.Version.Value.ToString(CultureInfo.InvariantCulture))); }
|
if (query.Version.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("version", query.Version.Value.ToString(CultureInfo.InvariantCulture))); }
|
||||||
if (query.ViewSecretValue.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("viewSecretValue", query.ViewSecretValue.Value ? "true" : "false")); }
|
if (query.ViewSecretValue.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("viewSecretValue", query.ViewSecretValue.Value ? "true" : "false")); }
|
||||||
if (query.ExpandSecretReferences.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("expandSecretReferences", query.ExpandSecretReferences.Value ? "true" : "false")); }
|
if (query.ExpandSecretReferences.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("expandSecretReferences", query.ExpandSecretReferences.Value ? "true" : "false")); }
|
||||||
if (query.IncludeImports.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("include_imports", query.IncludeImports.Value ? "true" : "false")); }
|
if (query.IncludeImports.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("includeImports", query.IncludeImports.Value ? "true" : "false")); }
|
||||||
|
|
||||||
Uri uri = InfisicalUriBuilder.Build(connection.BaseUri, definition, pathParameters, queryParameters);
|
|
||||||
InfisicalHttpResponse response = ExecuteAuthorized(connection, definition, "RetrieveSecret", uri, null);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.Information(Component, string.Concat("Attempting to retrieve Infisical secret '", query.SecretName, "'. Please Wait..."));
|
_logger.Information(Component, string.Concat("Attempting to retrieve Infisical secret '", query.SecretName, "'. Please Wait..."));
|
||||||
EnsureSuccess(response, definition);
|
|
||||||
|
InfisicalHttpResponse response = SendWithVersionFallback(
|
||||||
|
connection,
|
||||||
|
InfisicalEndpointNames.RetrieveSecret,
|
||||||
|
query.ApiVersion,
|
||||||
|
"RetrieveSecret",
|
||||||
|
pathParameters,
|
||||||
|
queryParameters,
|
||||||
|
null);
|
||||||
|
|
||||||
InfisicalSecretSingleResponseDto dto = _serializer.Deserialize<InfisicalSecretSingleResponseDto>(response.Body);
|
InfisicalSecretSingleResponseDto dto = _serializer.Deserialize<InfisicalSecretSingleResponseDto>(response.Body);
|
||||||
response.Clear();
|
response.Clear();
|
||||||
@@ -124,6 +136,160 @@ namespace PSInfisicalAPI.Secrets
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private InfisicalHttpResponse SendWithVersionFallback(
|
||||||
|
InfisicalConnection connection,
|
||||||
|
string endpointName,
|
||||||
|
string perCallApiVersion,
|
||||||
|
string operationName,
|
||||||
|
Dictionary<string, string> pathParameters,
|
||||||
|
List<KeyValuePair<string, string>> queryParameters,
|
||||||
|
string body)
|
||||||
|
{
|
||||||
|
IReadOnlyList<InfisicalEndpointDefinition> allCandidates = InfisicalEndpointRegistry.GetCandidates(endpointName);
|
||||||
|
|
||||||
|
string pinned = FirstNonEmpty(perCallApiVersion, connection.PinnedApiVersion);
|
||||||
|
string cached;
|
||||||
|
connection.ResolvedEndpointVersions.TryGetValue(endpointName, out cached);
|
||||||
|
|
||||||
|
List<InfisicalEndpointDefinition> candidates = OrderCandidates(allCandidates, pinned, cached);
|
||||||
|
|
||||||
|
if (candidates.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InfisicalConfigurationException(string.Concat(
|
||||||
|
"No matching endpoint candidate for '", endpointName,
|
||||||
|
"' with ApiVersion='", pinned ?? string.Empty, "'."));
|
||||||
|
}
|
||||||
|
|
||||||
|
InfisicalApiException lastException = null;
|
||||||
|
|
||||||
|
for (int index = 0; index < candidates.Count; index++)
|
||||||
|
{
|
||||||
|
InfisicalEndpointDefinition definition = candidates[index];
|
||||||
|
Uri uri = InfisicalUriBuilder.Build(connection.BaseUri, definition, pathParameters, queryParameters);
|
||||||
|
InfisicalHttpResponse response = ExecuteAuthorized(connection, definition, operationName, uri, body);
|
||||||
|
|
||||||
|
if (response.StatusCode >= 200 && response.StatusCode < 300)
|
||||||
|
{
|
||||||
|
connection.ResolvedEndpointVersions[endpointName] = definition.Version;
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
InfisicalApiException exception = BuildApiException(response, definition);
|
||||||
|
response.Clear();
|
||||||
|
|
||||||
|
bool hasMoreCandidates = (index + 1) < candidates.Count;
|
||||||
|
bool pinnedHere = !string.IsNullOrEmpty(pinned);
|
||||||
|
|
||||||
|
if (!pinnedHere && hasMoreCandidates && IsVersionMismatch(exception))
|
||||||
|
{
|
||||||
|
_logger.Warning(Component, string.Concat(
|
||||||
|
"Endpoint '", endpointName, "' version '", definition.Version,
|
||||||
|
"' rejected by server (", exception.StatusCode.ToString(CultureInfo.InvariantCulture),
|
||||||
|
"); falling back to next candidate."));
|
||||||
|
lastException = exception;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw lastException ?? new InfisicalApiException(string.Concat(
|
||||||
|
"All API version candidates exhausted for '", endpointName, "'."));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<InfisicalEndpointDefinition> OrderCandidates(
|
||||||
|
IReadOnlyList<InfisicalEndpointDefinition> allCandidates,
|
||||||
|
string pinned,
|
||||||
|
string cached)
|
||||||
|
{
|
||||||
|
List<InfisicalEndpointDefinition> ordered = new List<InfisicalEndpointDefinition>();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(pinned))
|
||||||
|
{
|
||||||
|
foreach (InfisicalEndpointDefinition candidate in allCandidates)
|
||||||
|
{
|
||||||
|
if (string.Equals(candidate.Version, pinned, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ordered.Add(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ordered;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(cached))
|
||||||
|
{
|
||||||
|
foreach (InfisicalEndpointDefinition candidate in allCandidates)
|
||||||
|
{
|
||||||
|
if (string.Equals(candidate.Version, cached, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ordered.Add(candidate);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (InfisicalEndpointDefinition candidate in allCandidates)
|
||||||
|
{
|
||||||
|
if (!string.Equals(candidate.Version, cached, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ordered.Add(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ordered;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (InfisicalEndpointDefinition candidate in allCandidates)
|
||||||
|
{
|
||||||
|
ordered.Add(candidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ordered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsVersionMismatch(InfisicalApiException exception)
|
||||||
|
{
|
||||||
|
string body = exception.SanitizedBody;
|
||||||
|
bool hasInfisicalErrorEnvelope = !string.IsNullOrEmpty(body)
|
||||||
|
&& body.IndexOf("\"reqId\"", StringComparison.OrdinalIgnoreCase) >= 0
|
||||||
|
&& body.IndexOf("\"error\"", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||||
|
|
||||||
|
if (exception.StatusCode == 405)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exception.StatusCode == 404 && !hasInfisicalErrorEnvelope)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exception.StatusCode == 400 && !string.IsNullOrEmpty(body))
|
||||||
|
{
|
||||||
|
if (body.IndexOf("projectSlug", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||||
|
body.IndexOf("workspaceId", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InfisicalApiException BuildApiException(InfisicalHttpResponse response, InfisicalEndpointDefinition definition)
|
||||||
|
{
|
||||||
|
InfisicalApiException exception = new InfisicalApiException(string.Concat(
|
||||||
|
"Infisical API returned ",
|
||||||
|
response.StatusCode.ToString(CultureInfo.InvariantCulture),
|
||||||
|
" (", response.ReasonPhrase ?? string.Empty, ")."));
|
||||||
|
exception.StatusCode = response.StatusCode;
|
||||||
|
exception.ReasonPhrase = response.ReasonPhrase;
|
||||||
|
exception.EndpointName = definition.Name;
|
||||||
|
exception.RequestMethod = definition.Method;
|
||||||
|
exception.SanitizedBody = response.Body;
|
||||||
|
return exception;
|
||||||
|
}
|
||||||
|
|
||||||
private InfisicalHttpResponse ExecuteAuthorized(InfisicalConnection connection, InfisicalEndpointDefinition definition, string operationName, Uri uri, string body)
|
private InfisicalHttpResponse ExecuteAuthorized(InfisicalConnection connection, InfisicalEndpointDefinition definition, string operationName, Uri uri, string body)
|
||||||
{
|
{
|
||||||
Dictionary<string, string> headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
Dictionary<string, string> headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -157,23 +323,6 @@ namespace PSInfisicalAPI.Secrets
|
|||||||
return _httpClient.Send(request);
|
return _httpClient.Send(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EnsureSuccess(InfisicalHttpResponse response, InfisicalEndpointDefinition definition)
|
|
||||||
{
|
|
||||||
if (response.StatusCode >= 200 && response.StatusCode < 300)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
InfisicalApiException exception = new InfisicalApiException(string.Concat("Infisical API returned ", response.StatusCode.ToString(CultureInfo.InvariantCulture), " (", response.ReasonPhrase ?? string.Empty, ")."));
|
|
||||||
exception.StatusCode = response.StatusCode;
|
|
||||||
exception.ReasonPhrase = response.ReasonPhrase;
|
|
||||||
exception.EndpointName = definition.Name;
|
|
||||||
exception.RequestMethod = definition.Method;
|
|
||||||
exception.SanitizedBody = definition.ContainsSecretMaterialInResponse ? "[REDACTED]" : response.Body;
|
|
||||||
response.Clear();
|
|
||||||
throw exception;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void AddIfNotNull(List<KeyValuePair<string, string>> list, string key, string value)
|
private static void AddIfNotNull(List<KeyValuePair<string, string>> list, string key, string value)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(value))
|
if (!string.IsNullOrEmpty(value))
|
||||||
|
|||||||
Reference in New Issue
Block a user