Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f86f527
🚀 [Feature]: Decompose the Confluence client into the PSModule source…
MariusStorhaug Jul 7, 2026
9be6481
🪲 [Fix]: Resolve PSScriptAnalyzer findings in examples and tests
MariusStorhaug Jul 7, 2026
fa9a9fa
Remove header.ps1 and move the module requirements to the REST entry …
MariusStorhaug Jul 7, 2026
04ae285
Remove the source manifest; tags come from repository topics
MariusStorhaug Jul 7, 2026
fdedf80
Match the GitHub module comment-based help style and link each comman…
MariusStorhaug Jul 7, 2026
3f82493
Merge origin/main into feat/decompose-module
MariusStorhaug Jul 7, 2026
fbfdad2
Green the source-code and docs pipelines
MariusStorhaug Jul 7, 2026
cb00658
Document the required Atlassian scopes for the service-account token
MariusStorhaug Jul 8, 2026
59542da
⚙️ [Maintenance]: Declare test secrets and variables via Process-PSMo…
MariusStorhaug Jul 8, 2026
71423fb
📖 [Docs]: Record configured token scopes and candidate commands in SC…
MariusStorhaug Jul 8, 2026
56b279a
🚀 [Feature]: Add ConvertTo-ConfluenceCloudId to resolve a site to its…
MariusStorhaug Jul 8, 2026
4e867fc
🚀 [Feature]: Connect by site name; add accessible-resources and v1/v2…
MariusStorhaug Jul 8, 2026
4fcca56
♻️ [Maintenance]: Rename ConvertTo-ConfluenceCloudId to Get-Confluenc…
MariusStorhaug Jul 8, 2026
9f23e48
♻️ [Maintenance]: Pass a single TestData secret to Process-PSModule (…
MariusStorhaug Jul 8, 2026
4cd4886
📖 [Docs]: Fix Connect-Confluence examples to use -Site/-CloudId and a…
MariusStorhaug Jul 8, 2026
e282457
🩹 [Patch]: Harden site URI parsing, surface site-info probe errors, a…
MariusStorhaug Jul 8, 2026
8b717c2
🩹 [Patch]: Preserve page status on update; trash-then-purge for Remov…
MariusStorhaug Jul 8, 2026
6712085
🩹 [Patch]: Skip integration tests unless all Confluence credentials a…
MariusStorhaug Jul 8, 2026
a8dc0d7
🩹 [Patch]: Allow compatible Context 8.x; document shortened scope for…
MariusStorhaug Jul 8, 2026
4a3ebf4
🩹 [Patch]: Use a concrete Context MaximumVersion so the module manife…
MariusStorhaug Jul 8, 2026
225a70b
🧪 [Test]: Exercise every command with live CRUD and context tests
MariusStorhaug Jul 9, 2026
85b625f
🩹 [Patch]: Retry attachment delete on transient 409 in tests
MariusStorhaug Jul 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions .github/workflows/Process-PSModule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,20 @@ permissions:

jobs:
Process-PSModule:
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@ce64918acc96dda73eb78f827036b794bfa6fa1a # v5.5.7
secrets: inherit
# Pinned to the TestData feature branch of Process-PSModule (PR #365).
# Repin to the released tag once that change ships.
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@ea019351821a74ae1528a4472198c84c91dd3059 # feat/52-test-secrets
secrets:
# PowerShell Gallery API key used to publish the module.
APIKey: ${{ secrets.APIKey }}
# All data the module's tests need, in one object: a "secrets" map (masked) and a "variables"
# map (not masked), each entry exposed as an environment variable. Only what the tests require
# is passed - secrets: inherit is intentionally not used. Integration tests skip when
# CONFLUENCE_API_TOKEN is unavailable (forks / PRs without access). Secrets use the direct
# "${{ secrets.X }}" form (CodeQL-clean) and the whole blob is one folded line so GitHub
# registers a single mask.
TestData: >-
{ "secrets": { "CONFLUENCE_API_TOKEN": "${{ secrets.CONFLUENCE_API_TOKEN }}" },
Comment thread
MariusStorhaug marked this conversation as resolved.
"variables": { "CONFLUENCE_SITE": ${{ toJSON(vars.CONFLUENCE_SITE) }},
"CONFLUENCE_USERNAME": ${{ toJSON(vars.CONFLUENCE_USERNAME) }},
"CONFLUENCE_SPACE_KEY": ${{ toJSON(vars.CONFLUENCE_SPACE_KEY) }} } }
46 changes: 41 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,51 @@
# Confluence

Confluence is intended to be a PowerShell module for interacting with Atlassian Confluence.
Confluence is a PowerShell module for interacting with the Atlassian Confluence Cloud REST API, both interactively and in automation.

## Status
It exposes pages, blog posts, folders, spaces, comments, labels, content properties, attachments, restrictions and more as PowerShell commands.
Every command is a thin wrapper over a single generic REST entry point (`Invoke-ConfluenceRestMethod`). Credentials and module configuration
are stored with the [Context](https://github.com/PSModule/Context) module, so you connect once and reuse the profile across sessions.

This repository is currently a placeholder. The module source still contains scaffold code and examples, so there are no supported Confluence-specific commands or usage examples to document yet.
## Installation

Install the module from the PowerShell Gallery:

```powershell
Install-PSResource -Name Confluence
Import-Module -Name Confluence
```

## Usage

Connect with a scoped Atlassian API token, then call the resource commands. The token is kept as a `SecureString` in the Context vault.

```powershell
# Connect and store a reusable, named credential profile
$token = Read-Host -AsSecureString # a scoped Atlassian API token
Connect-Confluence -Site 'yoursite' -Username 'you@example.com' -Token $token -SpaceKey 'DOCS'

# Work with content
$space = Get-ConfluenceSpace -Key 'DOCS'
$page = New-ConfluencePage -SpaceId $space.id -Title 'Release notes' -Body '<p>Hello</p>'
Set-ConfluencePage -PageId $page.id -Body '<p>Updated</p>'
Get-ConfluencePageChild -PageId $page.id
```

See the [examples](examples) folder for more, including managing contexts and pages.

The service-account token must be granted the module's required Confluence scopes — see [SCOPES.md](SCOPES.md).

## Documentation

When this module is implemented, command details should live in PowerShell help and generated documentation rather than being duplicated in this README.
Documentation is published at [psmodule.io/Confluence](https://psmodule.io/Confluence/).

Use PowerShell help and command discovery for module details:

```powershell
Get-Command -Module Confluence
Get-Help New-ConfluencePage -Examples
```

## Contributing

Issues and pull requests are welcome when implementation work begins.
Issues and pull requests are welcome. Please use the repository issue tracker to report bugs, request features, or discuss improvements.
158 changes: 158 additions & 0 deletions SCOPES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Required Atlassian scopes

`Confluence` authenticates with a **scoped** Atlassian API token (the `ATSTT…`
prefix) used as HTTP Basic authentication (`<service-account-email>:<token>`)
against the API-gateway host `https://api.atlassian.com/ex/confluence/<cloudId>`.

When you create the token for the service account, select **granular** Confluence
scopes (not the classic `*-confluence-*` scopes). Use this document when
generating or rotating a token.

> Note: cmdlet help lists scopes in a shortened form (for example `read:page`),
> omitting the `:confluence` suffix for brevity. The canonical, fully-qualified
> scope strings you grant on the token are the ones in this document (for example
> `read:page:confluence`).

## Configured scopes

The token is currently configured with **48** granular Confluence scopes
(**25 read**, **14 write**, **9 delete**). This is broader than the set the
module's commands use today (see the tables below), leaving headroom for new
commands.

### Read (25)

```text
read:analytics.content:confluence
read:attachment:confluence
read:comment:confluence
read:configuration:confluence
read:content:confluence
read:content-details:confluence
read:content.metadata:confluence
read:content.permission:confluence
read:content.property:confluence
read:content.restriction:confluence
read:custom-content:confluence
read:database:confluence
read:email-address:confluence
read:folder:confluence
read:group:confluence
read:hierarchical-content:confluence
read:label:confluence
read:page:confluence
read:permission:confluence
read:space:confluence
read:space-details:confluence
read:space.permission:confluence
read:space.property:confluence
read:space.setting:confluence
read:user:confluence
```

### Write (14)

```text
write:attachment:confluence
write:audit-log:confluence
write:comment:confluence
write:configuration:confluence
write:content:confluence
write:content.property:confluence
write:content.restriction:confluence
write:custom-content:confluence
write:database:confluence
write:embed:confluence
write:folder:confluence
write:group:confluence
write:label:confluence
write:page:confluence
```

### Delete (9)

```text
delete:attachment:confluence
delete:comment:confluence
delete:content:confluence
delete:custom-content:confluence
delete:database:confluence
delete:embed:confluence
delete:folder:confluence
delete:page:confluence
delete:whiteboard:confluence
```

## Scopes used by the current commands

| Scope | Used by |
| --- | --- |
| `read:space:confluence` | `Get-ConfluenceSpace`, `Get-ConfluenceSiteInfo` |
| `read:space.permission:confluence` | `Get-ConfluenceSpacePermission` |
| `read:space.property:confluence` | `Get-ConfluenceSpaceProperty` |
| `read:page:confluence` | `Get-ConfluencePage`, `Get-ConfluencePageChild`, `Get-ConfluencePageVersion`, `Set-ConfluencePage` (reads before updating) |
| `read:folder:confluence` | `Get-ConfluenceFolder` |
| `read:attachment:confluence` | `Get-ConfluenceAttachment` |
| `read:comment:confluence` | `Get-ConfluenceComment` |
| `read:label:confluence` | `Get-ConfluenceLabel` |
| `read:content.property:confluence` | `Get-ConfluenceContentProperty` |
| `read:hierarchical-content:confluence` | `Get-ConfluenceDescendant` |
| `read:content-details:confluence` | `Get-ConfluenceRestriction`, `Get-ConfluenceCurrentUser`, `Add-ConfluenceAttachment` (upload) |
| `write:page:confluence` | `New-ConfluencePage`, `Set-ConfluencePage` |
| `write:folder:confluence` | `New-ConfluenceFolder` |
| `write:attachment:confluence` | `Add-ConfluenceAttachment` |
| `write:comment:confluence` | `Add-ConfluenceComment` |
| `write:label:confluence` | `Add-ConfluenceLabel`, `Remove-ConfluenceLabel` |
| `write:content.property:confluence` | `Set-ConfluenceContentProperty`, `Remove-ConfluenceContentProperty` |
| `delete:page:confluence` | `Remove-ConfluencePage` |
| `delete:folder:confluence` | `Remove-ConfluenceFolder` |
| `delete:attachment:confluence` | `Remove-ConfluenceAttachment` |
| `delete:comment:confluence` | `Remove-ConfluenceComment` |

> Important: `Get-ConfluenceBlogPost` needs `read:blogpost:confluence`, which is
> **not** in the configured set. Add that scope (or retire the command) — the
> `/blogposts` endpoints will otherwise return 403. There is also no
> `write:blogpost` / `delete:blogpost`, so blog posts cannot be created or removed.
Comment thread
MariusStorhaug marked this conversation as resolved.

## Additional scopes available for new commands

These scopes are configured but not yet backed by a command. Each row lists the
candidate cmdlets it would enable (verbs in `{}` share one scope family):

| Scope(s) | Candidate commands |
| --- | --- |
| `{read,write,delete}:custom-content:confluence` | Full custom-content CRUD: `Get-`, `New-`, `Set-`, `Remove-ConfluenceCustomContent` |
| `{read,write,delete}:database:confluence` | Database CRUD: `Get-`, `New-`, `Set-`, `Remove-ConfluenceDatabase` |
| `read:group:confluence` + `write:group:confluence` | `Get-ConfluenceGroup`, `Get-ConfluenceGroupMember`, `New-ConfluenceGroup`, `Add-`/`Remove-ConfluenceGroupMember` |
| `read:user:confluence` + `read:email-address:confluence` | `Get-ConfluenceUser` (by accountId, incl. email); simplifies `Get-ConfluenceCurrentUser` |
| `{read,write}:content.restriction:confluence` | `Add-`, `Set-`, `Remove-ConfluenceRestriction` (complements the existing `Get-ConfluenceRestriction`) |
| `read:analytics.content:confluence` | `Get-ConfluenceContentAnalytics` (page/blog view + viewer counts) |
| `write:comment:confluence` | `Set-ConfluenceComment` (update an existing comment) |
| `read:permission:confluence` + `read:content.permission:confluence` | `Test-ConfluenceContentPermission` / `Get-ConfluenceOperationCheck` |
| `{read,write}:configuration:confluence` | `Get-`/`Set-ConfluenceConfiguration` (look-and-feel / system settings; needs admin) |
| `read:space.setting:confluence` + `read:space-details:confluence` | `Get-ConfluenceSpaceSetting`; richer `Get-ConfluenceSpace` details |
| `{read,write,delete}:content:confluence`, `read:content.metadata:confluence` | Generic/classic content search & operations, e.g. `Find-ConfluenceContent` (CQL) |
| `{write,delete}:embed:confluence` | `New-`/`Remove-ConfluenceEmbed` (Smart Links) — no `read:embed`, so read-back is unavailable |
| `delete:whiteboard:confluence` | `Remove-ConfluenceWhiteboard` only — no `read:`/`write:whiteboard`, so create/read is unavailable |
| `write:audit-log:confluence` | Audit-log export/write (admin) — no `read:audit-log`, so reading records is unavailable |

## Notes

- Scopes are only an **API ceiling**. Effective access is still bounded by the
service account's Confluence **permissions** (global and per-space), so grant it
the matching permissions on the spaces you target. Admin APIs (`configuration`,
`audit-log`, `group`) additionally need org/site admin rights.
- **Asymmetric sets** worth noting: `whiteboard` is delete-only; `embed` has
write/delete but no read; `audit-log` is write-only. Commands for those can only
cover the granted verbs.
- This set has **no space-administration write** scope (`write:space` /
`delete:space`): the token can read space settings, permissions and properties
but cannot create, update or delete spaces.
- A few commands reach v1 endpoints that Atlassian maps to these granular scopes:
label add/remove (`write:label:confluence`), attachment upload
(`write:attachment:confluence` plus `read:content-details:confluence`), page
restriction reads and the current user (`read:content-details:confluence`).
- Atlassian applies a soft cap of roughly **50 scopes** per token; at **48** this
set is near the cap, so adding the missing read scopes (blogpost, whiteboard,
embed) would leave little room and may require dropping unused ones.
- Reference: [Confluence OAuth 2.0 scopes](https://developer.atlassian.com/cloud/confluence/scopes-for-oauth-2-3LO-and-forge-apps/).
50 changes: 50 additions & 0 deletions examples/Connecting.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<#
.SYNOPSIS
Connect to Confluence and manage credential profiles (contexts).

.DESCRIPTION
Confluence stores each connection as a named context in the Context vault.
The token is kept as a SecureString. One context is the default; any command
can target a specific context with -Context.
#>

# Import the module
Import-Module -Name 'Confluence'

###
### CONNECTING
###

# Connect with a scoped Atlassian API token and store a reusable default profile.
# -Site takes a subdomain, host, or any URL on the site and resolves the cloud ID for you.
$token = Read-Host -AsSecureString # a scoped Atlassian API token
Connect-Confluence -Site 'yoursite' -Username 'you@example.com' -Token $token -SpaceKey 'DOCS'

# Store several sites/accounts under explicit names and select per call.
# If you already know the cloud ID, pass it directly with -CloudId to skip the lookup.
Connect-Confluence -CloudId '<cloudId>' -Username 'you@example.com' -Token $token -Name 'sandbox'
Get-ConfluenceSpace -Key 'DOCS' -Context 'sandbox'

###
### CONTEXTS / PROFILES
###

# The default context
Get-ConfluenceContext

# All stored contexts
Get-ConfluenceContext -ListAvailable

###
### MODULE CONFIGURATION
###

# Read and set module configuration (persisted in the same vault).
Get-ConfluenceConfig
Set-ConfluenceConfig -Name 'PerPage' -Value 50

###
### DISCONNECTING
###

Disconnect-Confluence -Name 'sandbox'
19 changes: 0 additions & 19 deletions examples/General.ps1

This file was deleted.

34 changes: 34 additions & 0 deletions examples/Pages.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<#
.SYNOPSIS
Create, read, update and remove Confluence pages.

.DESCRIPTION
Assumes a default context is already connected (see Connecting.ps1).
#>

# Import the module
Import-Module -Name 'Confluence'

# Resolve the target space
$space = Get-ConfluenceSpace -Key 'DOCS'

# Create a page
$page = New-ConfluencePage -SpaceId $space.id -Title 'Release notes' -Body '<p>First draft</p>'

# Read it back (storage format by default)
Get-ConfluencePage -PageId $page.id

# Update the body (the version number is incremented automatically)
Set-ConfluencePage -PageId $page.id -Body '<p>Updated content</p>'

# Add a child page, then list children and descendants
New-ConfluencePage -SpaceId $space.id -Title 'Details' -ParentId $page.id -Body '<p>Nested</p>'
Get-ConfluencePageChild -PageId $page.id
Get-ConfluenceDescendant -PageId $page.id

# Labels and comments
Add-ConfluenceLabel -PageId $page.id -Label 'release', 'published'
Add-ConfluenceComment -PageId $page.id -Body '<p>Looks good.</p>'

# Remove the whole subtree (moves the pages to trash)
Remove-ConfluencePage -PageId $page.id -Recurse
3 changes: 0 additions & 3 deletions src/finally.ps1

This file was deleted.

39 changes: 39 additions & 0 deletions src/functions/private/Auth/Resolve-ConfluenceContext.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
function Resolve-ConfluenceContext {
<#
.SYNOPSIS
Resolve a context argument into a usable credential-context object.

.DESCRIPTION
Accepts a context object/hashtable (returned as-is), a context name
(looked up in the vault), or nothing (falls back to the default context
recorded in the module configuration).
#>
[CmdletBinding()]
param(
# A context object, a context name, or $null for the default context.
[object]$Context
)

if ($null -ne $Context -and $Context -isnot [string]) {
return $Context
}

Initialize-ConfluenceConfig
$id = if ([string]::IsNullOrEmpty([string]$Context)) { $script:Confluence.Config['DefaultContext'] } else { [string]$Context }

if ([string]::IsNullOrEmpty($id)) {
throw 'No Confluence context was specified and no default context is set. Run Connect-Confluence first.'
}

$resolved = $null
try {
$resolved = Get-Context -ID $id -Vault $script:Confluence.ContextVault -ErrorAction Stop
} catch {
$resolved = $null
}

if (-not $resolved) {
throw "Confluence context '$id' was not found in vault '$($script:Confluence.ContextVault)'."
}
return $resolved
}
Loading
Loading