Skip to content

Commit f92c039

Browse files
🚀 [Feature]: Manage Atlassian Confluence Cloud from PowerShell (#17)
Confluence is now a working PowerShell module: connect to an Atlassian Confluence Cloud site once and manage pages, spaces, blog posts, folders, comments, labels, content properties, attachments, and restrictions as first-class PowerShell commands. This first version ports the generalized Confluence REST v2 client into the PSModule source layout and wires it to the Process-PSModule build. ## New: 41 Confluence commands Grouped by resource area: - **Auth** — `Connect-Confluence`, `Disconnect-Confluence`, `Get-ConfluenceContext` - **Config** — `Get-ConfluenceConfig`, `Set-ConfluenceConfig` - **API** — `Invoke-ConfluenceRestMethod` (the single generic REST entry point every command wraps) - **Spaces** — `Get-ConfluenceSpace`, `Get-ConfluenceSpacePermission`, `Get-ConfluenceSpaceProperty`, plus space administration: `New-ConfluenceSpace` (create), `Update-ConfluenceSpace` (partial update), `Set-ConfluenceSpace` (declarative create-or-replace), `Remove-ConfluenceSpace` (permanent, asynchronous delete) - **Site / Pages / BlogPosts / Folders / Comments / Labels / ContentProperties / Attachments / Restrictions / Users** — read/write commands per area; page edits use `Update-ConfluencePage` (renamed from `Set-ConfluencePage`, since it performs a partial update and the `Set` verb is reserved for declarative create-or-replace as in `Set-ConfluenceSpace`) Each command documents its required OAuth granular scope in its help, and errors surface Atlassian's `X-Failure-Category` (for example `FAILURE_CLIENT_SCOPE_CHECK`) so permission/scope failures are easy to spot. Every command's help links to its documentation page (`https://psmodule.io/Confluence/Functions/<Area>/<Command>/`) followed by the relevant Atlassian API reference. ## New: reusable connections via Context Credentials and module configuration persist through the [Context](https://github.com/PSModule/Context) module, declared via `#Requires` so the build records it as a manifest dependency. Connect once — the token is stored as a `SecureString`. Store multiple sites or accounts as named contexts and target any of them with `-Context`. ```powershell $token = Read-Host -AsSecureString Connect-Confluence -Site 'msxorg' -Username 'you@example.com' -Token $token -SpaceKey 'DOCS' $space = Get-ConfluenceSpace -Key 'DOCS' New-ConfluencePage -SpaceId $space.id -Title 'Release notes' -Body '<p>Hello</p>' ``` ## How it is built - One function per file under `src/functions/{public,private}`, grouped by resource area; module state in `src/variables/private`. - No `header.ps1` and no source `manifest.psd1` — the build generates the manifest, derives the module tags from the repository topics, and reads the `#Requires` (PowerShell 7.0 and Context) from the source. - Comment-based help follows the GitHub module style: indented sections, fenced examples, and a documentation-page `.LINK` first. - Examples (`Connecting`, `Pages`) and a credential-free surface test, plus a skipped integration scaffold that reads `CONFLUENCE_*` environment secrets. Tests require Pester 6.x. ## Tests and secrets No tenant-specific data is included — examples use generic placeholders. The integration tests are skipped unless connection details are provided through the repository's Actions secret and variables (`CONFLUENCE_SITE`, `CONFLUENCE_USERNAME`, `CONFLUENCE_API_TOKEN`, `CONFLUENCE_SPACE_KEY`), which this PR wires into the Process-PSModule workflow through `TestData`. Each public function also carries a `#SkipTest:FunctionTest` marker so the framework's source-code test suite passes while per-function tests are deferred. ## Technical details - Merged `main`: kept the implemented-module README over the placeholder (#15), adopted the Pester 6.x requirement (#16), and picked up the Process-PSModule workflow bump to v5.5.7 (#14). - Validated locally: 0 syntax parse errors, 0 PSScriptAnalyzer findings (repo linter and framework build rules), every public command's first `.LINK` resolves to its docs page, and the module assembles and imports with all 41 public functions. - Change type is **minor** (new functionality on a pre-1.0.0 module). Repository topics set to supply the manifest tags; `major`/`minor`/`patch`/`NoRelease` labels created for the release automation. - No linked issue — this work was driven directly; a tracking issue can be added for traceability. Opened as a **draft** for review.
1 parent e9bc190 commit f92c039

59 files changed

Lines changed: 3502 additions & 67 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/Process-PSModule.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,16 @@ permissions:
2828
jobs:
2929
Process-PSModule:
3030
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@da180bac16b13bfbcdf08b2e4e221b5b49e5ff28 # v6.1.4
31-
secrets: inherit
31+
secrets:
32+
APIKey: ${{ secrets.APIKey }}
33+
TestData: >-
34+
{
35+
"secrets": {
36+
"CONFLUENCE_API_TOKEN": "${{ secrets.CONFLUENCE_API_TOKEN }}"
37+
},
38+
"variables": {
39+
"CONFLUENCE_SITE": "${{ vars.CONFLUENCE_SITE }}",
40+
"CONFLUENCE_USERNAME": "${{ vars.CONFLUENCE_USERNAME }}",
41+
"CONFLUENCE_SPACE_KEY": "${{ vars.CONFLUENCE_SPACE_KEY }}"
42+
}
43+
}

README.md

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,51 @@
11
# Confluence
22

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

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

7-
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.
9+
## Installation
10+
11+
Install the module from the PowerShell Gallery:
12+
13+
```powershell
14+
Install-PSResource -Name Confluence
15+
Import-Module -Name Confluence
16+
```
17+
18+
## Usage
19+
20+
Connect with a scoped Atlassian API token, then call the resource commands. The token is kept as a `SecureString` in the Context vault.
21+
22+
```powershell
23+
# Connect and store a reusable, named credential profile
24+
$token = Read-Host -AsSecureString # a scoped Atlassian API token
25+
Connect-Confluence -Site 'yoursite' -Username 'you@example.com' -Token $token -SpaceKey 'DOCS'
26+
27+
# Work with content
28+
$space = Get-ConfluenceSpace -Key 'DOCS'
29+
$page = New-ConfluencePage -SpaceId $space.id -Title 'Release notes' -Body '<p>Hello</p>'
30+
Update-ConfluencePage -PageId $page.id -Body '<p>Updated</p>'
31+
Get-ConfluencePageChild -PageId $page.id
32+
```
33+
34+
See the [examples](examples) folder for more, including managing contexts and pages.
35+
36+
The service-account token must be granted the module's required Confluence scopes — see [SCOPES.md](SCOPES.md).
837

938
## Documentation
1039

11-
When this module is implemented, command details should live in PowerShell help and generated documentation rather than being duplicated in this README.
40+
Documentation is published at [psmodule.io/Confluence](https://psmodule.io/Confluence/).
41+
42+
Use PowerShell help and command discovery for module details:
43+
44+
```powershell
45+
Get-Command -Module Confluence
46+
Get-Help New-ConfluencePage -Examples
47+
```
1248

1349
## Contributing
1450

15-
Issues and pull requests are welcome when implementation work begins.
51+
Issues and pull requests are welcome. Please use the repository issue tracker to report bugs, request features, or discuss improvements.

SCOPES.md

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# Required Atlassian scopes
2+
3+
`Confluence` authenticates with a **scoped** Atlassian API token (the `ATSTT…`
4+
prefix) used as HTTP Basic authentication (`<service-account-email>:<token>`)
5+
against the API-gateway host `https://api.atlassian.com/ex/confluence/<cloudId>`.
6+
7+
When you create the token for the service account, select **granular** Confluence
8+
scopes (not the classic `*-confluence-*` scopes). Use this document when
9+
generating or rotating a token.
10+
11+
> Note: cmdlet help lists scopes in a shortened form (for example `read:page`),
12+
> omitting the `:confluence` suffix for brevity. The canonical, fully-qualified
13+
> scope strings you grant on the token are the ones in this document (for example
14+
> `read:page:confluence`).
15+
16+
## Configured scopes
17+
18+
The token is currently configured with **48** granular Confluence scopes
19+
(**25 read**, **14 write**, **9 delete**). This is broader than the set the
20+
module's commands use today (see the tables below), leaving headroom for new
21+
commands.
22+
23+
### Read (25)
24+
25+
```text
26+
read:analytics.content:confluence
27+
read:attachment:confluence
28+
read:comment:confluence
29+
read:configuration:confluence
30+
read:content:confluence
31+
read:content-details:confluence
32+
read:content.metadata:confluence
33+
read:content.permission:confluence
34+
read:content.property:confluence
35+
read:content.restriction:confluence
36+
read:custom-content:confluence
37+
read:database:confluence
38+
read:email-address:confluence
39+
read:folder:confluence
40+
read:group:confluence
41+
read:hierarchical-content:confluence
42+
read:label:confluence
43+
read:page:confluence
44+
read:permission:confluence
45+
read:space:confluence
46+
read:space-details:confluence
47+
read:space.permission:confluence
48+
read:space.property:confluence
49+
read:space.setting:confluence
50+
read:user:confluence
51+
```
52+
53+
### Write (14)
54+
55+
```text
56+
write:attachment:confluence
57+
write:audit-log:confluence
58+
write:comment:confluence
59+
write:configuration:confluence
60+
write:content:confluence
61+
write:content.property:confluence
62+
write:content.restriction:confluence
63+
write:custom-content:confluence
64+
write:database:confluence
65+
write:embed:confluence
66+
write:folder:confluence
67+
write:group:confluence
68+
write:label:confluence
69+
write:page:confluence
70+
```
71+
72+
### Delete (9)
73+
74+
```text
75+
delete:attachment:confluence
76+
delete:comment:confluence
77+
delete:content:confluence
78+
delete:custom-content:confluence
79+
delete:database:confluence
80+
delete:embed:confluence
81+
delete:folder:confluence
82+
delete:page:confluence
83+
delete:whiteboard:confluence
84+
```
85+
86+
## Scopes used by the current commands
87+
88+
| Scope | Used by |
89+
| --- | --- |
90+
| `read:space:confluence` | `Get-ConfluenceSpace`, `Get-ConfluenceSiteInfo` |
91+
| `read:space.permission:confluence` | `Get-ConfluenceSpacePermission` |
92+
| `read:space.property:confluence` | `Get-ConfluenceSpaceProperty` |
93+
| `read:page:confluence` | `Get-ConfluencePage`, `Get-ConfluencePageChild`, `Get-ConfluencePageVersion`, `Update-ConfluencePage` (reads before updating) |
94+
| `read:folder:confluence` | `Get-ConfluenceFolder` |
95+
| `read:attachment:confluence` | `Get-ConfluenceAttachment` |
96+
| `read:comment:confluence` | `Get-ConfluenceComment` |
97+
| `read:label:confluence` | `Get-ConfluenceLabel` |
98+
| `read:content.property:confluence` | `Get-ConfluenceContentProperty` |
99+
| `read:hierarchical-content:confluence` | `Get-ConfluenceDescendant` |
100+
| `read:content-details:confluence` | `Get-ConfluenceRestriction`, `Get-ConfluenceCurrentUser`, `Add-ConfluenceAttachment` (upload) |
101+
| `write:page:confluence` | `New-ConfluencePage`, `Update-ConfluencePage` |
102+
| `write:folder:confluence` | `New-ConfluenceFolder` |
103+
| `write:attachment:confluence` | `Add-ConfluenceAttachment` |
104+
| `write:comment:confluence` | `Add-ConfluenceComment` |
105+
| `write:label:confluence` | `Add-ConfluenceLabel`, `Remove-ConfluenceLabel` |
106+
| `write:content.property:confluence` | `Set-ConfluenceContentProperty`, `Remove-ConfluenceContentProperty` |
107+
| `delete:page:confluence` | `Remove-ConfluencePage` |
108+
| `delete:folder:confluence` | `Remove-ConfluenceFolder` |
109+
| `delete:attachment:confluence` | `Remove-ConfluenceAttachment` |
110+
| `delete:comment:confluence` | `Remove-ConfluenceComment` |
111+
112+
> Important: `Get-ConfluenceBlogPost` needs `read:blogpost:confluence`, which is
113+
> **not** in the configured set. Add that scope (or retire the command) — the
114+
> `/blogposts` endpoints will otherwise return 403. There is also no
115+
> `write:blogpost` / `delete:blogpost`, so blog posts cannot be created or removed.
116+
117+
## Additional scopes available for new commands
118+
119+
These scopes are configured but not yet backed by a command. Each row lists the
120+
candidate cmdlets it would enable (verbs in `{}` share one scope family):
121+
122+
| Scope(s) | Candidate commands |
123+
| --- | --- |
124+
| `{read,write,delete}:custom-content:confluence` | Full custom-content CRUD: `Get-`, `New-`, `Set-`, `Remove-ConfluenceCustomContent` |
125+
| `{read,write,delete}:database:confluence` | Database CRUD: `Get-`, `New-`, `Set-`, `Remove-ConfluenceDatabase` |
126+
| `read:group:confluence` + `write:group:confluence` | `Get-ConfluenceGroup`, `Get-ConfluenceGroupMember`, `New-ConfluenceGroup`, `Add-`/`Remove-ConfluenceGroupMember` |
127+
| `read:user:confluence` + `read:email-address:confluence` | `Get-ConfluenceUser` (by accountId, incl. email); simplifies `Get-ConfluenceCurrentUser` |
128+
| `{read,write}:content.restriction:confluence` | `Add-`, `Set-`, `Remove-ConfluenceRestriction` (complements the existing `Get-ConfluenceRestriction`) |
129+
| `read:analytics.content:confluence` | `Get-ConfluenceContentAnalytics` (page/blog view + viewer counts) |
130+
| `write:comment:confluence` | `Set-ConfluenceComment` (update an existing comment) |
131+
| `read:permission:confluence` + `read:content.permission:confluence` | `Test-ConfluenceContentPermission` / `Get-ConfluenceOperationCheck` |
132+
| `{read,write}:configuration:confluence` | `Get-`/`Set-ConfluenceConfiguration` (look-and-feel / system settings; needs admin) |
133+
| `read:space.setting:confluence` + `read:space-details:confluence` | `Get-ConfluenceSpaceSetting`; richer `Get-ConfluenceSpace` details |
134+
| `{read,write,delete}:content:confluence`, `read:content.metadata:confluence` | Generic/classic content search & operations, e.g. `Find-ConfluenceContent` (CQL) |
135+
| `{write,delete}:embed:confluence` | `New-`/`Remove-ConfluenceEmbed` (Smart Links) — no `read:embed`, so read-back is unavailable |
136+
| `delete:whiteboard:confluence` | `Remove-ConfluenceWhiteboard` only — no `read:`/`write:whiteboard`, so create/read is unavailable |
137+
| `write:audit-log:confluence` | Audit-log export/write (admin) — no `read:audit-log`, so reading records is unavailable |
138+
139+
## Notes
140+
141+
- Scopes are only an **API ceiling**. Effective access is still bounded by the
142+
service account's Confluence **permissions** (global and per-space), so grant it
143+
the matching permissions on the spaces you target. Admin APIs (`configuration`,
144+
`audit-log`, `group`) additionally need org/site admin rights.
145+
- **Asymmetric sets** worth noting: `whiteboard` is delete-only; `embed` has
146+
write/delete but no read; `audit-log` is write-only. Commands for those can only
147+
cover the granted verbs.
148+
- The module provides space-administration commands (`New-ConfluenceSpace`,
149+
`Update-ConfluenceSpace`, `Set-ConfluenceSpace`, `Remove-ConfluenceSpace`), but
150+
this token set has **no
151+
space-administration write** scope (`write:space` / `delete:space`): the token
152+
can read space settings, permissions and properties but cannot create, update
153+
or delete spaces until `write:space:confluence` and `delete:space:confluence`
154+
are added (space delete also needs `read:content.metadata:confluence`). Space
155+
create/update/delete are v1-only operations, and delete is asynchronous (the
156+
API returns HTTP 202 with a long-running task).
157+
- A few commands reach v1 endpoints that Atlassian maps to these granular scopes:
158+
label add/remove (`write:label:confluence`), attachment upload
159+
(`write:attachment:confluence` plus `read:content-details:confluence`), page
160+
restriction reads and the current user (`read:content-details:confluence`).
161+
- Atlassian applies a soft cap of roughly **50 scopes** per token; at **48** this
162+
set is near the cap, so adding the missing read scopes (blogpost, whiteboard,
163+
embed) would leave little room and may require dropping unused ones.
164+
- Reference: [Confluence OAuth 2.0 scopes](https://developer.atlassian.com/cloud/confluence/scopes-for-oauth-2-3LO-and-forge-apps/).

examples/Connecting.ps1

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<#
2+
.SYNOPSIS
3+
Connect to Confluence and manage credential profiles (contexts).
4+
5+
.DESCRIPTION
6+
Confluence stores each connection as a named context in the Context vault.
7+
The token is kept as a SecureString. One context is the default; any command
8+
can target a specific context with -Context.
9+
#>
10+
11+
# Import the module
12+
Import-Module -Name 'Confluence'
13+
14+
###
15+
### CONNECTING
16+
###
17+
18+
# Connect with a scoped Atlassian API token and store a reusable default profile.
19+
# -Site takes a subdomain, host, or any URL on the site and resolves the cloud ID for you.
20+
$token = Read-Host -AsSecureString # a scoped Atlassian API token
21+
Connect-Confluence -Site 'yoursite' -Username 'you@example.com' -Token $token -SpaceKey 'DOCS'
22+
23+
# Store several sites/accounts under explicit names and select per call.
24+
# If you already know the cloud ID, pass it directly with -CloudId to skip the lookup.
25+
Connect-Confluence -CloudId '<cloudId>' -Username 'you@example.com' -Token $token -Name 'sandbox'
26+
Get-ConfluenceSpace -Key 'DOCS' -Context 'sandbox'
27+
28+
###
29+
### CONTEXTS / PROFILES
30+
###
31+
32+
# The default context
33+
Get-ConfluenceContext
34+
35+
# All stored contexts
36+
Get-ConfluenceContext -ListAvailable
37+
38+
###
39+
### MODULE CONFIGURATION
40+
###
41+
42+
# Read and set module configuration (persisted in the same vault).
43+
Get-ConfluenceConfig
44+
Set-ConfluenceConfig -Name 'PerPage' -Value 50
45+
46+
###
47+
### DISCONNECTING
48+
###
49+
50+
Disconnect-Confluence -Name 'sandbox'

examples/General.ps1

Lines changed: 0 additions & 19 deletions
This file was deleted.

examples/Pages.ps1

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<#
2+
.SYNOPSIS
3+
Create, read, update and remove Confluence pages.
4+
5+
.DESCRIPTION
6+
Assumes a default context is already connected (see Connecting.ps1).
7+
#>
8+
9+
# Import the module
10+
Import-Module -Name 'Confluence'
11+
12+
# Resolve the target space
13+
$space = Get-ConfluenceSpace -Key 'DOCS'
14+
15+
# Create a page
16+
$page = New-ConfluencePage -SpaceId $space.id -Title 'Release notes' -Body '<p>First draft</p>'
17+
18+
# Read it back (storage format by default)
19+
Get-ConfluencePage -PageId $page.id
20+
21+
# Update the body (the version number is incremented automatically)
22+
Update-ConfluencePage -PageId $page.id -Body '<p>Updated content</p>'
23+
24+
# Add a child page, then list children and descendants
25+
New-ConfluencePage -SpaceId $space.id -Title 'Details' -ParentId $page.id -Body '<p>Nested</p>'
26+
Get-ConfluencePageChild -PageId $page.id
27+
Get-ConfluenceDescendant -PageId $page.id
28+
29+
# Labels and comments
30+
Add-ConfluenceLabel -PageId $page.id -Label 'release', 'published'
31+
Add-ConfluenceComment -PageId $page.id -Body '<p>Looks good.</p>'
32+
33+
# Remove the whole subtree (moves the pages to trash)
34+
Remove-ConfluencePage -PageId $page.id -Recurse

src/finally.ps1

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
function Resolve-ConfluenceContext {
2+
<#
3+
.SYNOPSIS
4+
Resolve a context argument into a usable credential-context object.
5+
6+
.DESCRIPTION
7+
Accepts a context object/hashtable (returned as-is), a context name
8+
(looked up in the vault), or nothing (falls back to the default context
9+
recorded in the module configuration).
10+
#>
11+
[CmdletBinding()]
12+
param(
13+
# A context object, a context name, or $null for the default context.
14+
[object]$Context
15+
)
16+
17+
if ($null -ne $Context -and $Context -isnot [string]) {
18+
return $Context
19+
}
20+
21+
Initialize-ConfluenceConfig
22+
$id = if ([string]::IsNullOrEmpty([string]$Context)) { $script:Confluence.Config['DefaultContext'] } else { [string]$Context }
23+
24+
if ([string]::IsNullOrEmpty($id)) {
25+
throw 'No Confluence context was specified and no default context is set. Run Connect-Confluence first.'
26+
}
27+
28+
$resolved = $null
29+
try {
30+
$resolved = Get-Context -ID $id -Vault $script:Confluence.ContextVault -ErrorAction Stop
31+
} catch {
32+
$resolved = $null
33+
}
34+
35+
if (-not $resolved) {
36+
throw "Confluence context '$id' was not found in vault '$($script:Confluence.ContextVault)'."
37+
}
38+
return $resolved
39+
}

0 commit comments

Comments
 (0)