459 lines
12 KiB
PowerShell
Executable file
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 `
|
|
{
|
|
|
|
}
|
|
}
|
|
}
|