init
This commit is contained in:
commit
17266ecb99
30 changed files with 1731 additions and 0 deletions
BIN
PSConfluencePublisher/._.DS_Store
Executable file
BIN
PSConfluencePublisher/._.DS_Store
Executable file
Binary file not shown.
BIN
PSConfluencePublisher/._Connection.Tests.ps1
Executable file
BIN
PSConfluencePublisher/._Connection.Tests.ps1
Executable file
Binary file not shown.
BIN
PSConfluencePublisher/._Connection.psm1
Executable file
BIN
PSConfluencePublisher/._Connection.psm1
Executable file
Binary file not shown.
BIN
PSConfluencePublisher/._Manifest.Tests.ps1
Executable file
BIN
PSConfluencePublisher/._Manifest.Tests.ps1
Executable file
Binary file not shown.
BIN
PSConfluencePublisher/._Manifest.psm1
Executable file
BIN
PSConfluencePublisher/._Manifest.psm1
Executable file
Binary file not shown.
BIN
PSConfluencePublisher/._PSConfluencePublisher.psd1
Executable file
BIN
PSConfluencePublisher/._PSConfluencePublisher.psd1
Executable file
Binary file not shown.
BIN
PSConfluencePublisher/._PSConfluencePublisher.psm1
Executable file
BIN
PSConfluencePublisher/._PSConfluencePublisher.psm1
Executable file
Binary file not shown.
BIN
PSConfluencePublisher/._Page.psm1
Executable file
BIN
PSConfluencePublisher/._Page.psm1
Executable file
Binary file not shown.
BIN
PSConfluencePublisher/._PersonalAccessToken.Tests.ps1
Executable file
BIN
PSConfluencePublisher/._PersonalAccessToken.Tests.ps1
Executable file
Binary file not shown.
BIN
PSConfluencePublisher/._PersonalAccessToken.psm1
Executable file
BIN
PSConfluencePublisher/._PersonalAccessToken.psm1
Executable file
Binary file not shown.
BIN
PSConfluencePublisher/._manifest.schema.json
Executable file
BIN
PSConfluencePublisher/._manifest.schema.json
Executable file
Binary file not shown.
BIN
PSConfluencePublisher/._scripts.deps.json
Executable file
BIN
PSConfluencePublisher/._scripts.deps.json
Executable file
Binary file not shown.
65
PSConfluencePublisher/Connection.Tests.ps1
Executable file
65
PSConfluencePublisher/Connection.Tests.ps1
Executable file
|
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
BeforeAll {
|
||||
Import-Module (Join-Path $PSScriptRoot 'PSConfluencePublisher.psd1') -Force
|
||||
}
|
||||
|
||||
|
||||
Describe 'Test-Connection' `
|
||||
{
|
||||
|
||||
Context 'Parameterized' {
|
||||
|
||||
It 'throws no exception' {
|
||||
|
||||
InModuleScope Connection {
|
||||
|
||||
Mock Get-PersonalAccessToken {'01234567890123456789'}
|
||||
|
||||
Mock Invoke-WebRequest {
|
||||
return @{
|
||||
'Content' = "{'type': 'known'}"
|
||||
'StatusCode' = 200
|
||||
}
|
||||
}
|
||||
|
||||
Test-Connection -Host 'confluence.contoso.com'
|
||||
}
|
||||
}
|
||||
|
||||
It 'detects anonymous authentication' {
|
||||
|
||||
InModuleScope Connection {
|
||||
|
||||
Mock Get-PersonalAccessToken {'01234567890123456789'}
|
||||
|
||||
Mock Invoke-WebRequest {
|
||||
return @{
|
||||
'Content' = "{'type': 'anonymous'}"
|
||||
'StatusCode' = 200
|
||||
}
|
||||
}
|
||||
|
||||
{Test-Connection -Host 'confluence.contoso.com'} | Should -Throw
|
||||
}
|
||||
}
|
||||
|
||||
It 'detects non 200 status codes' {
|
||||
|
||||
InModuleScope Connection {
|
||||
|
||||
Mock Get-PersonalAccessToken {'01234567890123456789'}
|
||||
|
||||
Mock Invoke-WebRequest {
|
||||
return @{
|
||||
'Content' = "{'type': 'anonymous'}"
|
||||
'StatusCode' = 500
|
||||
}
|
||||
}
|
||||
|
||||
{Test-Connection -Host 'confluence.contoso.com'} | Should -Throw
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
54
PSConfluencePublisher/Connection.psm1
Executable file
54
PSConfluencePublisher/Connection.psm1
Executable file
|
|
@ -0,0 +1,54 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
|
||||
function Test-Connection
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Test the connectivity to a Confluence instance.
|
||||
|
||||
.DESCRIPTION
|
||||
Just making an arbitrary authenticated HTTP request and making sure
|
||||
that we're getting a 2xx status code back. This way we make sure
|
||||
that network connectivity is fine, and that the PAT is valid.
|
||||
|
||||
It is required to register a PAT through
|
||||
``Register-PersonalAccessToken`` beforehand.
|
||||
|
||||
.EXAMPLE
|
||||
Test-Connection confluence.contoso.com
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory, Position = 0)] [string] $Host
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
# Screw Invoke-RestMethod, how am i supposed to get a non 4xx status
|
||||
# code? Catch a non-existent exception 🤷♀️????
|
||||
Invoke-WebRequest `
|
||||
-Uri "https://${Host}/rest/api/user/current" `
|
||||
-Method 'Get' `
|
||||
-Headers @{
|
||||
'Authorization' = "Bearer $(Get-PersonalAccessToken $Host)"
|
||||
} `
|
||||
-OutVariable response
|
||||
|
||||
if(($response.Content | ConvertFrom-JSON).type -ne "known")
|
||||
{
|
||||
throw "personal access token for host '$Host' does not " +
|
||||
"authenticate."
|
||||
}
|
||||
|
||||
if ($response.StatusCode -eq 200)
|
||||
{
|
||||
Write-Host "Verified connectivity ($Host)."
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "received status code other than 200 " +
|
||||
"($($response.StatusCode))"
|
||||
}
|
||||
}
|
||||
}
|
||||
70
PSConfluencePublisher/Manifest.Tests.ps1
Executable file
70
PSConfluencePublisher/Manifest.Tests.ps1
Executable file
|
|
@ -0,0 +1,70 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
BeforeAll {
|
||||
Import-Module (Join-Path $PSScriptRoot 'PSConfluencePublisher.psd1') -Force
|
||||
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
|
||||
}
|
||||
|
||||
|
||||
Describe 'Get-Manifest' `
|
||||
{
|
||||
|
||||
Context 'Parameterized' {
|
||||
|
||||
It 'throws no exception' {
|
||||
|
||||
InModuleScope Connection {
|
||||
|
||||
Mock Get-PersonalAccessToken {'01234567890123456789'}
|
||||
|
||||
Mock Invoke-WebRequest {
|
||||
return @{
|
||||
'Content' = "{'type': 'known'}"
|
||||
'StatusCode' = 200
|
||||
}
|
||||
}
|
||||
|
||||
Test-Connection -Host 'confluence.contoso.com'
|
||||
}
|
||||
}
|
||||
|
||||
It 'detects anonymous authentication' {
|
||||
|
||||
InModuleScope Connection {
|
||||
|
||||
Mock Get-PersonalAccessToken {'01234567890123456789'}
|
||||
|
||||
Mock Invoke-WebRequest {
|
||||
return @{
|
||||
'Content' = "{'type': 'anonymous'}"
|
||||
'StatusCode' = 200
|
||||
}
|
||||
}
|
||||
|
||||
{Test-Connection -Host 'confluence.contoso.com'} | Should -Throw
|
||||
}
|
||||
}
|
||||
|
||||
It 'detects non 200 status codes' {
|
||||
|
||||
InModuleScope Connection {
|
||||
|
||||
Mock Get-PersonalAccessToken {'01234567890123456789'}
|
||||
|
||||
Mock Invoke-WebRequest {
|
||||
return @{
|
||||
'Content' = "{'type': 'anonymous'}"
|
||||
'StatusCode' = 500
|
||||
}
|
||||
}
|
||||
|
||||
{Test-Connection -Host 'confluence.contoso.com'} | Should -Throw
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
74
PSConfluencePublisher/Manifest.psm1
Executable file
74
PSConfluencePublisher/Manifest.psm1
Executable file
|
|
@ -0,0 +1,74 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
|
||||
$script:schema = Get-Content (
|
||||
Join-Path $PSScriptRoot 'manifest.schema.json'
|
||||
) | Out-String
|
||||
|
||||
|
||||
function Get-Manifest
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Load the archive manifest
|
||||
|
||||
.EXAMPLE
|
||||
Get-Manifest 'manifest.json'
|
||||
#>
|
||||
Param(
|
||||
# filesystem location of manifest
|
||||
[Parameter(Mandatory)] [string] $File
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
try
|
||||
{
|
||||
$raw = Get-Content $File
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
$raw = '{"pages":{}, "attachments": {}}'
|
||||
}
|
||||
|
||||
$raw | Test-JSON -Schema $script:schema | Out-Null
|
||||
|
||||
$data = $raw | ConvertFrom-JSON
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Set-Manifest
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Dump the archive manifest
|
||||
|
||||
.EXAMPLE
|
||||
Set-Manifest 'manifest.json'
|
||||
#>
|
||||
Param(
|
||||
# manifest object
|
||||
[Parameter(Mandatory)] [PSObject] $Manifest,
|
||||
# filesystem location of manifest
|
||||
[Parameter(Mandatory)] [string] $File,
|
||||
# create a backup first
|
||||
[Parameter()] [bool] $Backup = $false
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
$raw = $Manifest | ConvertTo-JSON
|
||||
|
||||
$raw | Test-JSON -Schema $script:schema
|
||||
|
||||
if ($Backup)
|
||||
{
|
||||
Copy-Item -Path $File -Destination "$(Split-Path -Leaf $File).bck"
|
||||
}
|
||||
|
||||
Set-Content -Path $File -Value $raw
|
||||
}
|
||||
}
|
||||
131
PSConfluencePublisher/PSConfluencePublisher.psd1
Executable file
131
PSConfluencePublisher/PSConfluencePublisher.psd1
Executable file
|
|
@ -0,0 +1,131 @@
|
|||
@{
|
||||
|
||||
# Script module or binary module file associated with this manifest.
|
||||
#RootModule = 'ConfluencePublisher.psm1'
|
||||
|
||||
ModuleVersion = '1.1.0'
|
||||
|
||||
# Supported PSEditions
|
||||
# CompatiblePSEditions = @()
|
||||
|
||||
# ID used to uniquely identify this module
|
||||
GUID = 'b51d47f9-19b9-4c34-9a88-36eb8cf4c9bd'
|
||||
|
||||
# Author of this module
|
||||
Author = 'Theodor Rodweil'
|
||||
|
||||
# Company or vendor of this module
|
||||
CompanyName = 'Victory Karma IT'
|
||||
|
||||
# Copyright statement for this module
|
||||
Copyright = '(c) victory-k.it. All rights reserved.'
|
||||
|
||||
RootModule = 'PSConfluencePublisher.psm1'
|
||||
|
||||
# Description of the functionality provided by this module
|
||||
# Description = ''
|
||||
|
||||
# Minimum version of the PowerShell engine required by this module
|
||||
# PowerShellVersion = '6.0'
|
||||
|
||||
# Name of the PowerShell host required by this module
|
||||
# PowerShellHostName = ''
|
||||
|
||||
# Minimum version of the PowerShell host required by this module
|
||||
# PowerShellHostVersion = ''
|
||||
|
||||
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
|
||||
# DotNetFrameworkVersion = ''
|
||||
|
||||
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
|
||||
# ClrVersion = ''
|
||||
|
||||
# Processor architecture (None, X86, Amd64) required by this module
|
||||
# ProcessorArchitecture = ''
|
||||
|
||||
# Modules that must be imported into the global environment prior to importing this module
|
||||
# RequiredModules = @('sdf')
|
||||
|
||||
# Assemblies that must be loaded prior to importing this module
|
||||
# RequiredAssemblies = @()
|
||||
|
||||
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
|
||||
# ScriptsToProcess = @()
|
||||
|
||||
# Type files (.ps1xml) to be loaded when importing this module
|
||||
# TypesToProcess = @()
|
||||
|
||||
# Format files (.ps1xml) to be loaded when importing this module
|
||||
# FormatsToProcess = @()
|
||||
|
||||
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
|
||||
NestedModules = @(
|
||||
'PersonalAccessToken.psm1',
|
||||
'Connection.psm1',
|
||||
'Manifest.psm1'
|
||||
)
|
||||
|
||||
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
|
||||
FunctionsToExport = '*'
|
||||
|
||||
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
|
||||
CmdletsToExport = '*'
|
||||
|
||||
# Variables to export from this module
|
||||
VariablesToExport = '*'
|
||||
|
||||
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
|
||||
AliasesToExport = @()
|
||||
|
||||
# DSC resources to export from this module
|
||||
# DscResourcesToExport = @()
|
||||
|
||||
# List of all modules packaged with this module
|
||||
# ModuleList = @()
|
||||
|
||||
# List of all files packaged with this module
|
||||
FileList = @(
|
||||
"./manifest.schema.json"
|
||||
)
|
||||
|
||||
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
|
||||
PrivateData = @{
|
||||
|
||||
PSData = @{
|
||||
|
||||
# Tags applied to this module. These help with module discovery in online galleries.
|
||||
# Tags = @()
|
||||
|
||||
# A URL to the license for this module.
|
||||
# LicenseUri = ''
|
||||
|
||||
# A URL to the main website for this project.
|
||||
# ProjectUri = ''
|
||||
|
||||
# A URL to an icon representing this module.
|
||||
# IconUri = ''
|
||||
|
||||
# ReleaseNotes of this module
|
||||
# ReleaseNotes = ''
|
||||
|
||||
# Prerelease string of this module
|
||||
# Prerelease = ''
|
||||
|
||||
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
|
||||
# RequireLicenseAcceptance = $false
|
||||
|
||||
# External dependent modules of this module
|
||||
# ExternalModuleDependencies = @()
|
||||
|
||||
} # End of PSData hashtable
|
||||
|
||||
} # End of PrivateData hashtable
|
||||
|
||||
# HelpInfo URI of this module
|
||||
# HelpInfoURI = ''
|
||||
|
||||
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
|
||||
# DefaultCommandPrefix = ''
|
||||
|
||||
}
|
||||
|
||||
508
PSConfluencePublisher/PSConfluencePublisher.psm1
Executable file
508
PSConfluencePublisher/PSConfluencePublisher.psm1
Executable file
|
|
@ -0,0 +1,508 @@
|
|||
#!/usr/bin/env pwsh
|
||||
<#
|
||||
.SYNOPSIS
|
||||
PowerShell Publisher for sphinxcontrib.confluencebuilder
|
||||
|
||||
.DESCRIPTION
|
||||
|
||||
- support for ancestral pages and containered attachments
|
||||
- creates new pages if they don't exist
|
||||
- updates existing pages and attachments if checksum mismatches
|
||||
|
||||
.EXAMPLE
|
||||
|
||||
Import-Module (Join-Path 'vendor' 'tiara.rodney'
|
||||
'PSConfluencePublisher'
|
||||
'PSConfluencePublisher'
|
||||
'PSConfluencePublisher.psd1')
|
||||
|
||||
Register-PersonalAccessToken `
|
||||
-Host 'confluence.contoso.com' `
|
||||
-Token '123456789123456789'
|
||||
|
||||
Test-Connection confluence.contoso.com
|
||||
|
||||
Publish-All `
|
||||
-Url 'https://confluence.contoso.com/display/TIARA/Testitest' `
|
||||
-DumpIndex build/docs/confluence.out/data.json
|
||||
|
||||
.NOTES
|
||||
- tested with PowerShell Core (PSVersion 7.3.6)
|
||||
- tested with PowerShell Desktop (PSVersion 5.1.19041.3031)
|
||||
#>
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
|
||||
function New-ConfluencePage
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Add a confluence page
|
||||
|
||||
.DESCRIPTION
|
||||
|
||||
.EXAMPLE
|
||||
Add-ConfluencePage
|
||||
-Host 'confluence.contoso.com' `
|
||||
-Space 'TIARA' `
|
||||
-Title 'Testitest' `
|
||||
-Content @{}
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory)] [string] $Host,
|
||||
# The name of the Confluence space to publish to
|
||||
[Parameter(Mandatory)] [string] $Space,
|
||||
# title of page to be published
|
||||
[Parameter(Mandatory)] [string] $Title,
|
||||
# content of page
|
||||
[Parameter(Mandatory)] [string] $Content,
|
||||
# parent page id
|
||||
[Parameter()] [string] $Ancestor
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
Assert-PersonalAccessToken $Host
|
||||
|
||||
$transportBody = @{
|
||||
'type' = 'page'
|
||||
'title' = $Title
|
||||
'space' = @{
|
||||
'key' = $Space
|
||||
}
|
||||
'body' = @{
|
||||
'storage' = @{
|
||||
'value' = $Content
|
||||
'representation' = 'storage'
|
||||
}
|
||||
}
|
||||
} | ConvertTo-JSON
|
||||
|
||||
Invoke-WebRequest `
|
||||
-Uri "https://${Host}/rest/api/content" `
|
||||
-Method 'Post' `
|
||||
-Headers @{
|
||||
'Authorization' = "Bearer $([System.Net.NetworkCredential]::new('', $script:PATS[$Host_]).Password)"
|
||||
} `
|
||||
-ContentType "application/json" `
|
||||
-Body $transportBody `
|
||||
-OutVariable rawResponse | Out-Null
|
||||
}
|
||||
|
||||
End
|
||||
{
|
||||
$response = ($rawResponse.Content | ConvertFrom-JSON)
|
||||
|
||||
@{
|
||||
'PageId' = $response.Id
|
||||
'Version' = $response.version | Select -ExpandProperty 'number'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Update-Page
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Add a confluence page
|
||||
|
||||
.DESCRIPTION
|
||||
|
||||
.EXAMPLE
|
||||
Add-ConfluencePage
|
||||
-Host 'confluence.contoso.com' `
|
||||
-Space 'TIARA' `
|
||||
-Title 'Testitest' `
|
||||
-Content @{}
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory)] [string] $Host,
|
||||
# The page id of an existing page
|
||||
[Parameter(Mandatory)] [string] $PageId,
|
||||
# The name of the Confluence space to publish to
|
||||
[Parameter(Mandatory)] [string] $Space,
|
||||
# title of page to be published
|
||||
[Parameter(Mandatory)] [string] $Title,
|
||||
# version of content
|
||||
[Parameter(Mandatory)] [int] $Version,
|
||||
# content of page
|
||||
[Parameter(Mandatory)] [string] $Content,
|
||||
# parent page id
|
||||
[Parameter()] [string] $Ancestor
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
Assert-PersonalAccessToken $Host
|
||||
|
||||
$transportBody = @{
|
||||
'id' = $PageId
|
||||
'type' = 'page'
|
||||
'title' = $Title
|
||||
'space' = @{
|
||||
'key' = $Space
|
||||
}
|
||||
'body' = @{
|
||||
'storage' = @{
|
||||
'value' = $Content
|
||||
'representation' = 'storage'
|
||||
}
|
||||
}
|
||||
'version' = @{
|
||||
'number' = $Version
|
||||
}
|
||||
} | ConvertTo-JSON
|
||||
|
||||
Invoke-WebRequest `
|
||||
-Uri "https://${Host}/rest/api/content/$PageId" `
|
||||
-Method 'Put' `
|
||||
-Headers @{
|
||||
'Authorization' = "Bearer $([System.Net.NetworkCredential]::new('', $script:PATS[$Host_]).Password)"
|
||||
} `
|
||||
-ContentType "application/json" `
|
||||
-Body $transportBody `
|
||||
-OutVariable rawResponse | Out-Null
|
||||
}
|
||||
|
||||
End
|
||||
{
|
||||
$response = ($rawResponse.Content | ConvertFrom-JSON)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Get-PageMeta
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get a Confluence page id
|
||||
|
||||
.DESCRIPTION
|
||||
First, tries to retrieve from local page id index (cache) through
|
||||
the local alias. If no cache hit, then polls the Confluence
|
||||
instance host for the id by providing a space key and page title.
|
||||
|
||||
.EXAMPLE
|
||||
Get-PageMeta
|
||||
-Host 'confluence.contoso.com' `
|
||||
-Title 'Testitest' `
|
||||
-Space 'TIARA' `
|
||||
-CacheIndexFile 'confluence-page-cache.json'
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory)] [string] $Host,
|
||||
[Parameter(Mandatory)] [string] $Title,
|
||||
[Parameter(Mandatory)] [string] $Space,
|
||||
[Parameter(Mandatory)] [string] $CacheIndexFile
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
if ($Title)
|
||||
{
|
||||
$cachedPageMeta = Get-CachedPageMeta `
|
||||
-Title $Title `
|
||||
-CacheIndexFile $CacheIndexFile
|
||||
}
|
||||
|
||||
if ($cachedPageMeta)
|
||||
{
|
||||
return $cachedPageMeta
|
||||
}
|
||||
|
||||
$escapedTitle = [uri]::EscapeDataString($Title)
|
||||
|
||||
$query = "title=${escapedTitle}&spaceKey=${Space}&expand=history"
|
||||
|
||||
Assert-PersonalAccessToken $Host
|
||||
|
||||
Invoke-WebRequest `
|
||||
-Uri "https://${Host}/rest/api/content?$query" `
|
||||
-Method 'Get' `
|
||||
-Headers @{
|
||||
'Authorization' = "Bearer $([System.Net.NetworkCredential]::new('', $script:PATS[$Host_]).Password)"
|
||||
} `
|
||||
-OutVariable response
|
||||
|
||||
$results = ($response.Content | ConvertFrom-JSON).results
|
||||
|
||||
if ($results.Count -gt 1)
|
||||
{
|
||||
throw "more than one result for query: $query"
|
||||
}
|
||||
elseif ($results.Count -eq 1)
|
||||
{
|
||||
Register-PageMeta `
|
||||
-PageId $results[0].id `
|
||||
-Version ($results[0]._expandable | Select -ExpandProperty 'version') `
|
||||
-Title $Title `
|
||||
-CacheIndexFile $CacheIndexFile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Get-CachedPageMeta
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get a locally indexed/cached Confluence page id
|
||||
|
||||
.EXAMPLE
|
||||
Get-CachedPageMeta `
|
||||
-Title 'd231cc3422bfdf96.xml' `
|
||||
-CacheIndexFile 'confluence-page-cache.json'
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory)] [string] $Title,
|
||||
[Parameter(Mandatory)] [string] $CacheIndexFile
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
try
|
||||
{
|
||||
$raw = Get-Content $CacheIndexFile
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
$raw = "{}"
|
||||
}
|
||||
|
||||
$data = $raw | ConvertFrom-JSON
|
||||
|
||||
try
|
||||
{
|
||||
$pageMeta = $data | Select -ExpandProperty $Title
|
||||
|
||||
$pageMeta
|
||||
|
||||
Write-Debug "page id cache hit: $Title -> $($pageMeta.PageId)"
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
$null
|
||||
|
||||
Write-Debug "page id cache miss: $Title"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Register-PageMeta
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Register a Confluence page's metadata in the local cache
|
||||
|
||||
.DESCRIPTION
|
||||
|
||||
.EXAMPLE
|
||||
Add-ConfluencePage
|
||||
-Host 'confluence.contoso.com' `
|
||||
-Space 'TIARA' `
|
||||
-Title 'Testitest' `
|
||||
-Content @{}
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory)] [string] $PageId,
|
||||
[Parameter()] [int] $Version = 0,
|
||||
[Parameter(Mandatory)] [string] $Title,
|
||||
[Parameter()] [string] $ContentHash = '',
|
||||
[Parameter(Mandatory)] [string] $CacheIndexFile
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
try
|
||||
{
|
||||
$raw = Get-Content $CacheIndexFile
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
$raw = "{}"
|
||||
}
|
||||
|
||||
$data = $raw | ConvertFrom-JSON
|
||||
|
||||
$data | Add-Member -Name $Title `
|
||||
-Value @{
|
||||
'PageId' = $PageId
|
||||
'Version' = $Version
|
||||
'ContentHash' = $ContentHash
|
||||
} `
|
||||
-MemberType NoteProperty `
|
||||
-Force
|
||||
|
||||
Set-Content -Path $CacheIndexFile -Value ($data | ConvertTo-JSON)
|
||||
|
||||
Write-Debug "indexed page id: $Title -> $PageId"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Publish-Page
|
||||
{
|
||||
Param(
|
||||
# title of the page (used for manifest lookup)
|
||||
[Parameter(Mandatory)] [string] $Title,
|
||||
# hostname of Confluence instance
|
||||
[Parameter(Mandatory)] [string] $Host,
|
||||
# name of Confluence space
|
||||
[Parameter(Mandatory)] [string] $Space,
|
||||
# manifest object
|
||||
[Parameter(Mandatory)] [PSObject] $Manifest
|
||||
)
|
||||
|
||||
Begin
|
||||
{
|
||||
$pageMeta = Get-PageMeta `
|
||||
-Host $hostname `
|
||||
-Space $spaceName `
|
||||
-Title $Title `
|
||||
-Manifest $Manifest
|
||||
}
|
||||
|
||||
Process
|
||||
{
|
||||
if ($pageMeta.ContentHash -eq $_)
|
||||
{
|
||||
Write-Host "skipping (no changes): $Title"
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
$pageId = $pageMeta.PageId
|
||||
|
||||
$path = Join-Path $basepath 'content' "$_"
|
||||
|
||||
$pageContent = Get-Content $path | Out-String
|
||||
|
||||
$prettyName = $Title
|
||||
|
||||
if ($data.pages[$_].ancestor_id)
|
||||
{
|
||||
$ancestorTitle = $data.pages[$data.pages[$_].ancestor_id].title
|
||||
|
||||
$ancestorPageMeta = Get-PageMeta `
|
||||
-Host $hostname `
|
||||
-Space $spaceName `
|
||||
-Title $ancestorTitle `
|
||||
-CacheIndexFile $cacheIndexFile
|
||||
|
||||
if ($ancestorPageMeta)
|
||||
{
|
||||
$ancestorPageId = $ancestorPageMeta.PageId
|
||||
}
|
||||
|
||||
$prettyName += " [$ancestorPageId]"
|
||||
}
|
||||
|
||||
if (-Not $pageId)
|
||||
{
|
||||
Write-Host ("create ${_}: $prettyName")
|
||||
|
||||
try {
|
||||
$pageMeta = New-ConfluencePage `
|
||||
-Host $hostname `
|
||||
-Space $spaceName `
|
||||
-Title $pageTitle `
|
||||
-Content $pageContent `
|
||||
-Ancestor $ancestorPageId
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
Write-Host "error (skipping): $prettyName"
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Register-PageMeta `
|
||||
-PageId $pageMeta.PageId `
|
||||
-Version $pageMeta.Version `
|
||||
-Title $pageTitle `
|
||||
-ContentHash $_ `
|
||||
-CacheIndexFile $cacheIndexFile
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host ("update ${_} (${pageId}): $prettyName")
|
||||
|
||||
$version = $pageMeta.Version + 1
|
||||
|
||||
try
|
||||
{
|
||||
Update-Page `
|
||||
-Host $hostname `
|
||||
-PageId $pageId `
|
||||
-Space $spaceName `
|
||||
-Title $pageTitle `
|
||||
-Version $version `
|
||||
-Content $pageContent `
|
||||
-Ancestor $ancestorPageId
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
Write-Host "error (skipping): $prettyName"
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
Register-PageMeta `
|
||||
-PageId $pageMeta.PageId `
|
||||
-Version $version `
|
||||
-Title $pageTitle `
|
||||
-ContentHash $_ `
|
||||
-CacheIndexFile $cacheIndexFile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Publish-All
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
1. cast index hash table to array
|
||||
2. (quick) sort the array
|
||||
|
||||
.EXAMPLE
|
||||
Get-Help -Name Test-Help
|
||||
|
||||
This shows the help for the example function.
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory, Position = 0)] [string] $Url,
|
||||
[Parameter(Mandatory, Position = 1)] [string] $Manifest
|
||||
)
|
||||
|
||||
Begin
|
||||
{
|
||||
$hostname = ([uri]$url).Host
|
||||
|
||||
$spaceName = (Split-Path -Leaf (Split-Path $Url))
|
||||
|
||||
$ancestorName = Split-Path -Leaf $url
|
||||
|
||||
$data = Get-Content -Raw $Manifest | ConvertFrom-JSON -AsHashtable
|
||||
|
||||
$basepath = Split-Path $Manifest
|
||||
|
||||
$cacheIndexFile = 'confluence-page-cache.json'
|
||||
}
|
||||
|
||||
Process
|
||||
{
|
||||
$data.pages.keys | ForEach-Object `
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
435
PSConfluencePublisher/Page.psm1
Executable file
435
PSConfluencePublisher/Page.psm1
Executable file
|
|
@ -0,0 +1,435 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
|
||||
function Get-CachedPageMeta
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get a locally indexed/cached Confluence page id
|
||||
|
||||
.EXAMPLE
|
||||
Get-CachedPageMeta `
|
||||
-Title 'd231cc3422bfdf96.xml' `
|
||||
-CacheIndexFile 'confluence-page-cache.json'
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory)] [string] $Title,
|
||||
[Parameter(Mandatory)] [string] $CacheIndexFile
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
try
|
||||
{
|
||||
$raw = Get-Content $CacheIndexFile
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
$raw = "{}"
|
||||
}
|
||||
|
||||
$data = $raw | ConvertFrom-JSON
|
||||
|
||||
try
|
||||
{
|
||||
$pageMeta = $data | Select -ExpandProperty $Title
|
||||
|
||||
$pageMeta
|
||||
|
||||
Write-Debug "page id cache hit: $Title -> $($pageMeta.PageId)"
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
$null
|
||||
|
||||
Write-Debug "page id cache miss: $Title"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Get-PageMeta
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get a Confluence page id
|
||||
|
||||
.DESCRIPTION
|
||||
First, tries to retrieve from local page id index (cache) through
|
||||
the local alias. If no cache hit, then polls the Confluence
|
||||
instance host for the id by providing a space key and page title.
|
||||
|
||||
.EXAMPLE
|
||||
Get-PageMeta
|
||||
-Host 'confluence.contoso.com' `
|
||||
-Title 'Testitest' `
|
||||
-Space 'TIARA' `
|
||||
-CacheIndexFile 'confluence-page-cache.json'
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory)] [string] $Host,
|
||||
[Parameter(Mandatory)] [string] $Title,
|
||||
[Parameter(Mandatory)] [string] $Space,
|
||||
[Parameter(Mandatory)] [string] $CacheIndexFile
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
if ($Title)
|
||||
{
|
||||
$cachedPageMeta = Get-CachedPageMeta `
|
||||
-Title $Title `
|
||||
-CacheIndexFile $CacheIndexFile
|
||||
}
|
||||
|
||||
if ($cachedPageMeta)
|
||||
{
|
||||
return $cachedPageMeta
|
||||
}
|
||||
|
||||
$escapedTitle = [uri]::EscapeDataString($Title)
|
||||
|
||||
$query = "title=${escapedTitle}&spaceKey=${Space}&expand=history"
|
||||
|
||||
Assert-PersonalAccessToken $Host
|
||||
|
||||
Invoke-WebRequest `
|
||||
-Uri "https://${Host}/rest/api/content?$query" `
|
||||
-Method 'Get' `
|
||||
-Headers @{
|
||||
'Authorization' = "Bearer $([System.Net.NetworkCredential]::new('', $script:PATS[$Host_]).Password)"
|
||||
} `
|
||||
-OutVariable response
|
||||
|
||||
$results = ($response.Content | ConvertFrom-JSON).results
|
||||
|
||||
if ($results.Count -gt 1)
|
||||
{
|
||||
throw "more than one result for query: $query"
|
||||
}
|
||||
elseif ($results.Count -eq 1)
|
||||
{
|
||||
Register-PageMeta `
|
||||
-PageId $results[0].id `
|
||||
-Version ($results[0]._expandable | Select -ExpandProperty 'version') `
|
||||
-Title $Title `
|
||||
-CacheIndexFile $CacheIndexFile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Register-PageMeta
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Register a Confluence page's metadata in the local cache
|
||||
|
||||
.DESCRIPTION
|
||||
|
||||
.EXAMPLE
|
||||
Add-ConfluencePage
|
||||
-Host 'confluence.contoso.com' `
|
||||
-Space 'TIARA' `
|
||||
-Title 'Testitest' `
|
||||
-Content @{}
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory)] [string] $PageId,
|
||||
[Parameter()] [int] $Version = 0,
|
||||
[Parameter(Mandatory)] [string] $Title,
|
||||
[Parameter()] [string] $ContentHash = '',
|
||||
[Parameter(Mandatory)] [string] $CacheIndexFile
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
try
|
||||
{
|
||||
$raw = Get-Content $CacheIndexFile
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
$raw = "{}"
|
||||
}
|
||||
|
||||
$data = $raw | ConvertFrom-JSON
|
||||
|
||||
$data | Add-Member -Name $Title `
|
||||
-Value @{
|
||||
'PageId' = $PageId
|
||||
'Version' = $Version
|
||||
'ContentHash' = $ContentHash
|
||||
} `
|
||||
-MemberType NoteProperty `
|
||||
-Force
|
||||
|
||||
Set-Content -Path $CacheIndexFile -Value ($data | ConvertTo-JSON)
|
||||
|
||||
Write-Debug "indexed page id: $Title -> $PageId"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function New-Page
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Add a confluence page
|
||||
|
||||
.DESCRIPTION
|
||||
|
||||
.EXAMPLE
|
||||
Add-ConfluencePage
|
||||
-Host 'confluence.contoso.com' `
|
||||
-Space 'TIARA' `
|
||||
-Title 'Testitest' `
|
||||
-Content @{}
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory)] [string] $Host,
|
||||
# The name of the Confluence space to publish to
|
||||
[Parameter(Mandatory)] [string] $Space,
|
||||
# title of page to be published
|
||||
[Parameter(Mandatory)] [string] $Title,
|
||||
# content of page
|
||||
[Parameter(Mandatory)] [string] $Content,
|
||||
# parent page id
|
||||
[Parameter()] [string] $Ancestor
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
Assert-PersonalAccessToken $Host
|
||||
|
||||
$transportBody = @{
|
||||
'type' = 'page'
|
||||
'title' = $Title
|
||||
'space' = @{
|
||||
'key' = $Space
|
||||
}
|
||||
'body' = @{
|
||||
'storage' = @{
|
||||
'value' = $Content
|
||||
'representation' = 'storage'
|
||||
}
|
||||
}
|
||||
} | ConvertTo-JSON
|
||||
|
||||
Invoke-WebRequest `
|
||||
-Uri "https://${Host}/rest/api/content" `
|
||||
-Method 'Post' `
|
||||
-Headers @{
|
||||
'Authorization' = "Bearer $([System.Net.NetworkCredential]::new('', $script:PATS[$Host_]).Password)"
|
||||
} `
|
||||
-ContentType "application/json" `
|
||||
-Body $transportBody `
|
||||
-OutVariable rawResponse | Out-Null
|
||||
}
|
||||
|
||||
End
|
||||
{
|
||||
$response = ($rawResponse.Content | ConvertFrom-JSON)
|
||||
|
||||
@{
|
||||
'PageId' = $response.Id
|
||||
'Version' = $response.version | Select -ExpandProperty 'number'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Update-Page
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Add a confluence page
|
||||
|
||||
.DESCRIPTION
|
||||
|
||||
.EXAMPLE
|
||||
Add-ConfluencePage
|
||||
-Host 'confluence.contoso.com' `
|
||||
-Space 'TIARA' `
|
||||
-Title 'Testitest' `
|
||||
-Content @{}
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory)] [string] $Host,
|
||||
# The page id of an existing page
|
||||
[Parameter(Mandatory)] [string] $PageId,
|
||||
# The name of the Confluence space to publish to
|
||||
[Parameter(Mandatory)] [string] $Space,
|
||||
# title of page to be published
|
||||
[Parameter(Mandatory)] [string] $Title,
|
||||
# version of content
|
||||
[Parameter(Mandatory)] [int] $Version,
|
||||
# content of page
|
||||
[Parameter(Mandatory)] [string] $Content,
|
||||
# parent page id
|
||||
[Parameter()] [string] $Ancestor
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
Assert-PersonalAccessToken $Host
|
||||
|
||||
$transportBody = @{
|
||||
'id' = $PageId
|
||||
'type' = 'page'
|
||||
'title' = $Title
|
||||
'space' = @{
|
||||
'key' = $Space
|
||||
}
|
||||
'body' = @{
|
||||
'storage' = @{
|
||||
'value' = $Content
|
||||
'representation' = 'storage'
|
||||
}
|
||||
}
|
||||
'version' = @{
|
||||
'number' = $Version
|
||||
}
|
||||
} | ConvertTo-JSON
|
||||
|
||||
Invoke-WebRequest `
|
||||
-Uri "https://${Host}/rest/api/content/$PageId" `
|
||||
-Method 'Put' `
|
||||
-Headers @{
|
||||
'Authorization' = "Bearer $([System.Net.NetworkCredential]::new('', $script:PATS[$Host_]).Password)"
|
||||
} `
|
||||
-ContentType "application/json" `
|
||||
-Body $transportBody `
|
||||
-OutVariable rawResponse | Out-Null
|
||||
}
|
||||
|
||||
End
|
||||
{
|
||||
$response = ($rawResponse.Content | ConvertFrom-JSON)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Publish-Page
|
||||
{
|
||||
Param(
|
||||
# title of the page (used for manifest lookup)
|
||||
[Parameter(Mandatory)] [string] $Title,
|
||||
# hostname of Confluence instance
|
||||
[Parameter(Mandatory)] [string] $Host,
|
||||
# name of Confluence space
|
||||
[Parameter(Mandatory)] [string] $Space,
|
||||
# manifest object
|
||||
[Parameter(Mandatory)] [PSObject] $Manifest
|
||||
)
|
||||
|
||||
Begin
|
||||
{
|
||||
$pageMeta = Get-PageMeta `
|
||||
-Host $hostname `
|
||||
-Space $spaceName `
|
||||
-Title $Title `
|
||||
-Manifest $Manifest
|
||||
}
|
||||
|
||||
Process
|
||||
{
|
||||
if ($pageMeta.ContentHash -eq $_)
|
||||
{
|
||||
Write-Host "skipping (no changes): $Title"
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
$pageId = $pageMeta.PageId
|
||||
|
||||
$path = Join-Path $basepath 'content' "$_"
|
||||
|
||||
$pageContent = Get-Content $path | Out-String
|
||||
|
||||
$prettyName = $Title
|
||||
|
||||
if ($data.pages[$_].ancestor_id)
|
||||
{
|
||||
$ancestorTitle = $data.pages[$data.pages[$_].ancestor_id].title
|
||||
|
||||
$ancestorPageMeta = Get-PageMeta `
|
||||
-Host $hostname `
|
||||
-Space $spaceName `
|
||||
-Title $ancestorTitle `
|
||||
-CacheIndexFile $cacheIndexFile
|
||||
|
||||
if ($ancestorPageMeta)
|
||||
{
|
||||
$ancestorPageId = $ancestorPageMeta.PageId
|
||||
}
|
||||
|
||||
$prettyName += " [$ancestorPageId]"
|
||||
}
|
||||
|
||||
if (-Not $pageId)
|
||||
{
|
||||
Write-Host ("create ${_}: $prettyName")
|
||||
|
||||
try {
|
||||
$pageMeta = New-Page `
|
||||
-Host $hostname `
|
||||
-Space $spaceName `
|
||||
-Title $pageTitle `
|
||||
-Content $pageContent `
|
||||
-Ancestor $ancestorPageId
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
Write-Host "error (skipping): $prettyName"
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Register-PageMeta `
|
||||
-PageId $pageMeta.PageId `
|
||||
-Version $pageMeta.Version `
|
||||
-Title $pageTitle `
|
||||
-ContentHash $_ `
|
||||
-CacheIndexFile $cacheIndexFile
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host ("update ${_} (${pageId}): $prettyName")
|
||||
|
||||
$version = $pageMeta.Version + 1
|
||||
|
||||
try
|
||||
{
|
||||
Update-Page `
|
||||
-Host $hostname `
|
||||
-PageId $pageId `
|
||||
-Space $spaceName `
|
||||
-Title $pageTitle `
|
||||
-Version $version `
|
||||
-Content $pageContent `
|
||||
-Ancestor $ancestorPageId
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
Write-Host "error (skipping): $prettyName"
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
Register-PageMeta `
|
||||
-PageId $pageMeta.PageId `
|
||||
-Version $version `
|
||||
-Title $pageTitle `
|
||||
-ContentHash $_ `
|
||||
-CacheIndexFile $cacheIndexFile
|
||||
}
|
||||
}
|
||||
}
|
||||
65
PSConfluencePublisher/PersonalAccessToken.Tests.ps1
Executable file
65
PSConfluencePublisher/PersonalAccessToken.Tests.ps1
Executable file
|
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
BeforeAll {
|
||||
Import-Module (Join-Path $PSScriptRoot 'PSConfluencePublisher.psd1') -Force
|
||||
|
||||
$mockHost = 'confluence.contoso.com'
|
||||
|
||||
$mockPat = '01234567890123456789'
|
||||
}
|
||||
|
||||
|
||||
Describe 'Register-PersonalAccessToken' `
|
||||
{
|
||||
BeforeEach {
|
||||
Initialize-PersonalAccessTokenStore
|
||||
}
|
||||
|
||||
Context 'Parameterized' {
|
||||
|
||||
It 'throws no exception' {
|
||||
Register-PersonalAccessToken -Host $mockHost -Token $mockPat
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Shorthand' {
|
||||
|
||||
It 'throws no exception' {
|
||||
Register-PersonalAccessToken $mockHost $mockPat
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Describe 'Get-PersonalAccessToken' `
|
||||
{
|
||||
BeforeEach {
|
||||
Initialize-PersonalAccessTokenStore
|
||||
}
|
||||
|
||||
Context 'Parameterized' {
|
||||
|
||||
It 'gets an existing PAT' {
|
||||
|
||||
Register-PersonalAccessToken -Host $mockHost -Token $mockPat
|
||||
|
||||
Get-PersonalAccessToken -Host $mockHost | Should -Be $mockPat
|
||||
}
|
||||
|
||||
It 'requires PAT to exist' {
|
||||
|
||||
{Get-PersonalAccessToken -Host $mockHost} | Should -Throw
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Shorthand' {
|
||||
|
||||
It 'throws no exception' {
|
||||
|
||||
Register-PersonalAccessToken -Host $mockHost -Token $mockPat
|
||||
|
||||
Get-PersonalAccessToken $mockHost | Should -Be $mockPat
|
||||
}
|
||||
}
|
||||
}
|
||||
99
PSConfluencePublisher/PersonalAccessToken.psm1
Executable file
99
PSConfluencePublisher/PersonalAccessToken.psm1
Executable file
|
|
@ -0,0 +1,99 @@
|
|||
#!/usr/bin/env pwsh
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Utilities for working with Confluence Personal Access Tokens
|
||||
|
||||
.DESCRIPTION
|
||||
|
||||
|
||||
|
||||
.EXAMPLE
|
||||
|
||||
Register-PersonalAccessToken `
|
||||
-Host 'confluence.contoso.com' `
|
||||
-Token '123456789123456789'
|
||||
|
||||
Get-PersonalAccessToken -Host 'confluence.contoso.com'
|
||||
#>
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
|
||||
#session storage of Confluence personal access tokens
|
||||
$script:PATS = @{}
|
||||
|
||||
|
||||
function Initialize-PersonalAccessTokenStore
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Initialize the store within this script's scope.
|
||||
|
||||
.EXAMPLE
|
||||
Initialize-PersonalAccessTokenStore
|
||||
#>
|
||||
Process
|
||||
{
|
||||
$script:PATS = @{}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Register-PersonalAccessToken
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Register a Confluence Personal Access Token (PAT)
|
||||
|
||||
.DESCRIPTION
|
||||
The PAT is stored in the pseudo-local ``script`` scope as a
|
||||
SecureString. Implementors of functions accessing PATs MUST stall
|
||||
conversion to plain text string until the string is actually needed
|
||||
|
||||
.EXAMPLE
|
||||
Register-PersonalAccessToken confluence.contoso.com 0123456789
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
|
||||
Param(
|
||||
[Parameter(Mandatory, Position = 0)] [string] $Host,
|
||||
[Parameter(Mandatory, Position = 1)] [string] $Token
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
if ($script:PATS[$Host])
|
||||
{
|
||||
Write-Debug "PAT for '$Host' already registered, overwriting."
|
||||
}
|
||||
|
||||
$script:PATS[$Host] = ConvertTo-SecureString $Token -AsPlainText -Force
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Get-PersonalAccessToken
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get a Confluence Personal Access Token (PAT) registered in this
|
||||
script scope.
|
||||
|
||||
.EXAMPLE
|
||||
Get-PersonalAccessToken confluence.contoso.com
|
||||
#>
|
||||
Param(
|
||||
# Confluence instance hostname
|
||||
[Parameter(Mandatory, Position = 0)] [string] $Host
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
if (-Not $PATS[$Host])
|
||||
{
|
||||
throw "No personal access token for host '$Host' registered. " +
|
||||
"Hint: Call ``Register-PersonalAccessToken``"
|
||||
}
|
||||
|
||||
$([Net.NetworkCredential]::new('', $script:PATS[$Host]).Password)
|
||||
}
|
||||
}
|
||||
BIN
PSConfluencePublisher/_mock/._test-manifest1.json
Executable file
BIN
PSConfluencePublisher/_mock/._test-manifest1.json
Executable file
Binary file not shown.
BIN
PSConfluencePublisher/_mock/._test-manifest2.json
Executable file
BIN
PSConfluencePublisher/_mock/._test-manifest2.json
Executable file
Binary file not shown.
4
PSConfluencePublisher/_mock/test-manifest1.json
Executable file
4
PSConfluencePublisher/_mock/test-manifest1.json
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"pages": {},
|
||||
"attachments": {}
|
||||
}
|
||||
12
PSConfluencePublisher/_mock/test-manifest2.json
Executable file
12
PSConfluencePublisher/_mock/test-manifest2.json
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"pages": {
|
||||
"": {
|
||||
"Ref": ""
|
||||
}
|
||||
},
|
||||
"attachments": {
|
||||
"": {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
93
PSConfluencePublisher/manifest.schema.json
Executable file
93
PSConfluencePublisher/manifest.schema.json
Executable file
|
|
@ -0,0 +1,93 @@
|
|||
{
|
||||
"$id": "https://spec.victory-k.it/psconfluencepublisher.json",
|
||||
"x-authors": [
|
||||
"theodor.rodweil@victory-k.it"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pages": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
".*": {
|
||||
"$ref": "#/definitions/page"
|
||||
}
|
||||
}
|
||||
},
|
||||
"attachments": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
".*": {
|
||||
"$ref": "#/definitions/attachment"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"pages",
|
||||
"attachments"
|
||||
],
|
||||
"definitions": {
|
||||
"page": {
|
||||
"type": "object",
|
||||
"description": "Local Confluence page/container attachment metadata",
|
||||
"properties": {
|
||||
"PageId": {
|
||||
"type": "string",
|
||||
"description": "Id of attachment defined by Confluence instance. The id is generated after the publishing of a page."
|
||||
},
|
||||
"Version": {
|
||||
"type": "string"
|
||||
},
|
||||
"Hash": {
|
||||
"type": "string",
|
||||
"description": "SHA512 hexadecimal content hash value"
|
||||
},
|
||||
"Ref": {
|
||||
"type": "string",
|
||||
"description": "Local filesystem reference/path"
|
||||
},
|
||||
"AncestorTitle": {
|
||||
"type": "string",
|
||||
"description": "Title of Confluence page this page is a child of. The title must be a property key of the pages object."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"Hash",
|
||||
"Ref"
|
||||
]
|
||||
},
|
||||
"attachment": {
|
||||
"type": "object",
|
||||
"description": "Local Confluence page/container attachment metadata",
|
||||
"properties": {
|
||||
"AttachmentId": {
|
||||
"type": "string",
|
||||
"description": "Id of attachment defined by Confluence instance. The id is generated after the publishing of an attachment."
|
||||
},
|
||||
"Hash": {
|
||||
"type": "string",
|
||||
"description": "SHA512 hexadecimal attachment content hash value"
|
||||
},
|
||||
"MimeType": {
|
||||
"type": "string",
|
||||
"description": "MIME type of attachment",
|
||||
"default": "binary/octet-stream"
|
||||
},
|
||||
"ContainerPageTitle": {
|
||||
"type": "string",
|
||||
"description": "Title of Confluence page this attachment is contained in. The title must be a property key of the pages object."
|
||||
},
|
||||
"Ref": {
|
||||
"type": "string",
|
||||
"description": "Local filesystem reference/path"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"Hash",
|
||||
"MimeType",
|
||||
"ContainerPageTitle",
|
||||
"Ref"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
19
PSConfluencePublisher/scripts.deps.json
Executable file
19
PSConfluencePublisher/scripts.deps.json
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"runtimeTarget": {
|
||||
"name": ".NETStandard,Version=v2.0/",
|
||||
"signature": ""
|
||||
},
|
||||
"compilationOptions": {},
|
||||
"targets": {
|
||||
".NETStandard,Version=v2.0": {},
|
||||
".NETStandard,Version=v2.0/": {
|
||||
"PSConfluencePublisher/1.0.0": {
|
||||
"dependencies": {
|
||||
"NETStandard.Library": "2.0.3"
|
||||
},
|
||||
"runtime": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"libraries": {}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue