psconfluencepublisher/PSConfluencePublisher/PSConfluencePublisher.psm1
2023-08-06 04:14:27 +02:00

459 lines
12 KiB
PowerShell
Executable file

#!/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 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 `
{
}
}
}