refactor(Page): convert functions to be usable with pipelines this isn't complete though, will finish this tomorrow
446 lines
12 KiB
PowerShell
Executable file
446 lines
12 KiB
PowerShell
Executable file
#!/usr/bin/env pwsh
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
|
|
function Get-CachedPageMeta
|
|
{
|
|
<#
|
|
.SYNOPSIS
|
|
Get a locally indexed/cached Confluence page id
|
|
|
|
.EXAMPLE
|
|
Get-CachedPageMeta `
|
|
-Title 'Page Title' `
|
|
-Manifest @{...}
|
|
|
|
.NOTES
|
|
To test or not to test, that is the question... Since the
|
|
`Test-JSON` cmdlet requires serialized JSON, but we are working with
|
|
the deserialized Hashtable, it's too computationally intense to
|
|
always test the input upon every call. We therefore only make sure,
|
|
that correct data is written to the filesystem. For the rest, each
|
|
function is responsible for themself (learned that that's a valid
|
|
reflexive pronoun today 🤓).
|
|
|
|
This function is lucky to get this note, because it's at the top 💯.
|
|
Of course this applies to every function.
|
|
#>
|
|
Param(
|
|
[Parameter(Mandatory)] [string] $Title,
|
|
[Parameter(Mandatory)] [Collections.Hashtable] $Manifest
|
|
)
|
|
|
|
Process
|
|
{
|
|
#it's fine this fails, if no `Pages` property is provided, since the
|
|
#object (according to the schema) would be invalid anyway.
|
|
$pages = $Manifest | Select -ExpandProperty 'Pages'
|
|
|
|
try
|
|
{
|
|
$pageMeta = $pages | Select -ExpandProperty $Title
|
|
|
|
Write-Debug "page id cache hit: $Title -> $($pageMeta.PageId)"
|
|
|
|
$pageMeta
|
|
}
|
|
|
|
catch
|
|
{
|
|
Write-Debug "page id cache miss: $Title"
|
|
|
|
$null
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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)] [Collections.Hashtable] $Manifest
|
|
)
|
|
|
|
Process
|
|
{
|
|
if ($Title)
|
|
{
|
|
$cachedPageMeta = Get-CachedPageMeta `
|
|
-Title $Title `
|
|
-Manifest $Manifest
|
|
}
|
|
|
|
if ($cachedPageMeta)
|
|
{
|
|
return $cachedPageMeta
|
|
}
|
|
|
|
$escapedTitle = [uri]::EscapeDataString($Title)
|
|
|
|
#TODO: move this to a separate function
|
|
$query = "title=${escapedTitle}&spaceKey=${Space}&expand=history"
|
|
|
|
Invoke-WebRequest `
|
|
-Uri "https://${Host}/rest/api/content?$query" `
|
|
-Method 'Get' `
|
|
-Headers @{
|
|
'Authorization' = "Bearer $(Get-PersonalAccessToken $Host)"
|
|
} `
|
|
-OutVariable response | Out-Null
|
|
|
|
$results = ($response.Content | ConvertFrom-JSON).results
|
|
|
|
if ($results.Count -gt 1)
|
|
{
|
|
throw "more than one result for query: $query"
|
|
}
|
|
|
|
elseif ($results.Count -eq 1)
|
|
{
|
|
Update-PageMeta `
|
|
-PageId $results[0].id `
|
|
-Version ($results[0]._expandable | Select -ExpandProperty 'version') `
|
|
-Title $Title `
|
|
-Manifest $Manifest
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
function Update-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] $Title,
|
|
[Parameter(Mandatory)] [string] $PageId,
|
|
[Parameter()] [int] $Version,
|
|
[Parameter()] [string] $AncestorTitle,
|
|
[Parameter()] [string] $Hash,
|
|
[Parameter(Mandatory)] [Collections.Hashtable] $Manifest
|
|
)
|
|
|
|
Process
|
|
{
|
|
$metaPages = $Manifest.Pages
|
|
|
|
if ((-Not $metaPages) -Or (-Not $metaPages.$Title))
|
|
{
|
|
throw "page titled `$Title` not indexed in Manifest."
|
|
}
|
|
|
|
$meta = $metaPages.$Title
|
|
|
|
$meta['PageId'] = $PageId
|
|
|
|
if ($Version)
|
|
{
|
|
$meta['Version'] = $Version
|
|
}
|
|
|
|
if ($AncestorTitle)
|
|
{
|
|
$meta['AncestorTitle'] = $AncestorTitle
|
|
}
|
|
|
|
# if content didn't update, hash stays the same
|
|
if ($Hash)
|
|
{
|
|
$meta['Hash'] = $Hash
|
|
}
|
|
|
|
Write-Debug "register: $Title -> $PageId"
|
|
}
|
|
}
|
|
|
|
|
|
function New-Page
|
|
{
|
|
<#
|
|
.SYNOPSIS
|
|
Add a confluence page
|
|
|
|
.DESCRIPTION
|
|
|
|
.EXAMPLE
|
|
Add-ConfluencePage `
|
|
-Host 'confluence.contoso.com' `
|
|
-Space 'TIARA' `
|
|
-Title 'Testitest' `
|
|
-Content @{}
|
|
#>
|
|
Param(
|
|
# confluence instance hostname
|
|
[Parameter(Mandatory)] [string] $Host,
|
|
# name of the Confluence space to publish to
|
|
[Parameter(Mandatory)] [string] $Space,
|
|
# title of page to be published
|
|
[Parameter(Mandatory)] [string] $Title,
|
|
# manifest
|
|
[Parameter(Mandatory)] [Collections.Hashtable] $Manifest
|
|
)
|
|
|
|
Process
|
|
{
|
|
$meta = $Manifest.Pages.$Title
|
|
|
|
if (-Not $meta.Ref)
|
|
{
|
|
throw "no reference to local content for page `$Title`."
|
|
}
|
|
|
|
$content = Get-Content -Path $meta.Ref
|
|
|
|
$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 $(Get-PersonalAccessToken $Host)"
|
|
} `
|
|
-ContentType "application/json" `
|
|
-Body $transportBody `
|
|
-OutVariable rawResponse | Out-Null
|
|
}
|
|
|
|
End
|
|
{
|
|
$response = ($rawResponse.Content | ConvertFrom-JSON)
|
|
|
|
$meta.PageId = $response.Id
|
|
|
|
$meta.Version = $response.version | Select -ExpandProperty 'number'
|
|
|
|
$meta
|
|
}
|
|
}
|
|
|
|
|
|
function Update-Page
|
|
{
|
|
<#
|
|
.SYNOPSIS
|
|
Add a confluence page
|
|
|
|
.DESCRIPTION
|
|
|
|
.EXAMPLE
|
|
Update-ConfluencePage
|
|
-Host 'confluence.contoso.com' `
|
|
-Space 'TIARA' `
|
|
-Title 'Testitest' `
|
|
-Manifest @{}
|
|
#>
|
|
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,
|
|
# manifest
|
|
[Parameter(Mandatory)] [Collections.Hashtable] $Meta
|
|
)
|
|
|
|
Process
|
|
{
|
|
$meta = $Manifest.Pages.$Title
|
|
|
|
if (-Not $meta.Ref)
|
|
{
|
|
throw "no reference to local content for page '$Title'."
|
|
}
|
|
|
|
if (-Not $meta.Id)
|
|
{
|
|
throw "no id for page '$Title'."
|
|
}
|
|
|
|
$content = Get-Content -Path $meta.Ref
|
|
|
|
#FIXME: create a stream instead of reading from filesystem again
|
|
$hash = (Get-FileHash -Path $meta.Ref -Algorithm SHA256).Hash
|
|
|
|
if ($hash -eq $meta.Hash)
|
|
{
|
|
Write-Host "content unchanged, skipping: '$Title'"
|
|
|
|
# yep, this is funny... This behaves like a return statement, because
|
|
# a cmdlet, treats the input as an array of inputs. We keep it that
|
|
# way so that all functions can properly act upon pipes. See
|
|
# additional information on 'Process' blocks.
|
|
continue
|
|
}
|
|
|
|
# we're not updating this in place, so that we don't have to reset the
|
|
# value opon failure
|
|
$version = $meta.Version + 1
|
|
|
|
$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 $(Get-PersonalAccessToken $Host)"
|
|
} `
|
|
-ContentType "application/json" `
|
|
-Body $transportBody `
|
|
-OutVariable rawResponse | Out-Null
|
|
}
|
|
|
|
End
|
|
{
|
|
$response = ($rawResponse.Content | ConvertFrom-JSON)
|
|
|
|
$meta.Version = $response.version | Select -ExpandProperty 'number'
|
|
|
|
$meta.Hash = $hash
|
|
|
|
$meta
|
|
}
|
|
}
|
|
|
|
|
|
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, ValueFromPipeline)] [PSObject] $Meta
|
|
)
|
|
|
|
Process
|
|
{
|
|
ForEach($meta in $Meta)
|
|
{
|
|
$meta = Get-PageMeta `
|
|
-Host $hostname `
|
|
-Space $spaceName `
|
|
-Title $Title `
|
|
-Manifest $Manifest
|
|
|
|
if ($meta.AncestorTitle)
|
|
{
|
|
$ancestorPageMeta = Get-PageMeta `
|
|
-Host $hostname `
|
|
-Space $spaceName `
|
|
-Title $pageMeta.AncestorTitle `
|
|
-Manifest $Manifest
|
|
|
|
if (-Not ($ancestorPageMeta -Or $ancestorPageMeta.PageId))
|
|
{
|
|
Write-Host "ancestor, not published, skipping: $Title"
|
|
|
|
continue
|
|
}
|
|
}
|
|
|
|
if (-Not $pageId)
|
|
{
|
|
Write-Host ("create ${_}: $prettyName")
|
|
|
|
try {
|
|
New-Page `
|
|
-Host $hostname `
|
|
-Space $spaceName `
|
|
-Title $Title `
|
|
-Manifest $Manifest
|
|
}
|
|
|
|
catch
|
|
{
|
|
Write-Host "error for '$Title', skipping: $_"
|
|
|
|
continue
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
Write-Host ("update ${_} (${pageId}): $prettyName")
|
|
|
|
try
|
|
{
|
|
Update-Page `
|
|
-Host $hostname `
|
|
-Space $Space `
|
|
-Title $Title `
|
|
-Manifest $Manifest
|
|
}
|
|
|
|
catch
|
|
{
|
|
Write-Host "error for '$Title', skipping: $_"
|
|
|
|
continue
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|