From c59a4b2b9e0afdf6dd72ad99f49e3297c487bfa6 Mon Sep 17 00:00:00 2001 From: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Date: Thu, 20 Apr 2023 10:10:26 -0500 Subject: [PATCH 1/8] Added support for Confluence Cloud Signed-off-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> --- .changeset/famous-days-kneel.md | 5 +++ packages/backend/package.json | 1 + packages/backend/src/plugins/scaffolder.ts | 26 +++++++++++- .../README.md | 41 ++++++++++++++++--- .../confluence/confluenceToMarkdown.ts | 3 +- .../src/actions/confluence/helpers.test.ts | 40 ++++++++++++++++++ .../src/actions/confluence/helpers.ts | 24 ++++++++--- yarn.lock | 3 +- 8 files changed, 129 insertions(+), 14 deletions(-) create mode 100644 .changeset/famous-days-kneel.md create mode 100644 plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts diff --git a/.changeset/famous-days-kneel.md b/.changeset/famous-days-kneel.md new file mode 100644 index 0000000000..8aef0f4da8 --- /dev/null +++ b/.changeset/famous-days-kneel.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch +--- + +Added support for Confluence Cloud to the `confluence:transform:markdown` action, view the [README](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-confluence-to-markdown) for more details diff --git a/packages/backend/package.json b/packages/backend/package.json index f225e35d98..dc3d313e8b 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -60,6 +60,7 @@ "@backstage/plugin-proxy-backend": "workspace:^", "@backstage/plugin-rollbar-backend": "workspace:^", "@backstage/plugin-scaffolder-backend": "workspace:^", + "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "workspace:^", "@backstage/plugin-scaffolder-backend-module-rails": "workspace:^", "@backstage/plugin-search-backend": "workspace:^", "@backstage/plugin-search-backend-module-elasticsearch": "workspace:^", diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts index 821e5c1adf..505a514344 100644 --- a/packages/backend/src/plugins/scaffolder.ts +++ b/packages/backend/src/plugins/scaffolder.ts @@ -15,9 +15,14 @@ */ import { CatalogClient } from '@backstage/catalog-client'; -import { createRouter } from '@backstage/plugin-scaffolder-backend'; +import { + createBuiltinActions, + createRouter, +} from '@backstage/plugin-scaffolder-backend'; import { Router } from 'express'; import type { PluginEnvironment } from '../types'; +import { ScmIntegrations } from '@backstage/integration'; +import { createConfluenceToMarkdownAction } from '@backstage/plugin-scaffolder-backend-module-confluence-to-markdown'; export default async function createPlugin( env: PluginEnvironment, @@ -26,6 +31,24 @@ export default async function createPlugin( discoveryApi: env.discovery, }); + const integrations = ScmIntegrations.fromConfig(env.config); + + const builtInActions = createBuiltinActions({ + integrations, + config: env.config, + catalogClient, + reader: env.reader, + }); + + const actions = [ + ...builtInActions, + createConfluenceToMarkdownAction({ + integrations, + config: env.config, + reader: env.reader, + }), + ]; + return await createRouter({ logger: env.logger, config: env.config, @@ -35,5 +58,6 @@ export default async function createPlugin( identity: env.identity, scheduler: env.scheduler, permissions: env.permissions, + actions, }); } diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/README.md b/plugins/scaffolder-backend-module-confluence-to-markdown/README.md index d9b235d6c1..ee04f1650e 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/README.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/README.md @@ -4,16 +4,18 @@ Welcome to the `confluence:transform:markdown` action for the `scaffolder-backen ## Getting started -You need to configure the action in your backend: +The following sections will help you getting started -## From your Backstage root directory +### Configure Action in Backend + +From your Backstage root directory run: ```bash # From your Backstage root directory yarn add --cwd packages/backend @backstage/plugin-scaffolder-backend-module-confluence-to-markdown ``` -Configure the action: +Then configure the action: (you can check the [docs](https://backstage.io/docs/features/software-templates/writing-custom-actions#registering-custom-actions) to see all options): ```typescript @@ -56,6 +58,8 @@ export default async function createPlugin( } ``` +### Configuration + You will also need an access token for authorization with `Read` permissions. You can create a Personal Access Token (PAT) in confluence and add the PAT to your `app-config.yaml` ```yaml @@ -64,7 +68,34 @@ confluence: token: ${CONFLUENCE_TOKEN} ``` -After that you can use the action in your template: +#### Confluence Cloud + +For those using Confluence Cloud you will need to have the following configuration: + +```yaml +confluence: + baseUrl: ${CONFLUENCE_BASE_URL} + token: ${CONFLUENCE_TOKEN} + isCloud: true +``` + +##### baseUrl + +The `baseUrl` for Confluence Cloud should include the product name which is `wiki` by default but can be something else if your Org has changed it. An example `baseUrl` for Confluence Cloud would look like this: `https://example.atlassian.net/wiki` + +##### token + +The `token` for Confluence Cloud needs to be base-64 encoded with your Atlassian account email address. Here's how to do that: + +1. First get your token from: `https://.atlassian.com/manage-profile/security/api-tokens` +2. Next we need to setup a string in this format: `:` +3. For this example we'll use this: `confluence@backstage.io:wDzAzoXWRGLtvbgHvT0W` +4. Now we can run `echo -n "confluence@backstage.io:wDzAzoXWRGLtvbgHvT0W" | base64` +5. This gives us: `Y29uZmx1ZW5jZUBiYWNrc3RhZ2UuaW86d0R6QXpvWFdSR0x0dmJnSHZUMFc=` which we can now use as the value for the `token` in the configuration + +### Template Usage + +Here's an example of how you can use the action in your template: ```yaml apiVersion: scaffolder.backstage.io/v1beta3 @@ -84,7 +115,7 @@ spec: properties: confluenceUrls: type: array - description: Urls for confluence doc to be converted to markdown. In format /display// + description: Urls for confluence doc to be converted to markdown. In format /display// or /spaces//pages// for Confluence cloud items: type: string default: confluence url diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts index 801f8abb28..5e8553e426 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts @@ -57,7 +57,7 @@ export const createConfluenceToMarkdownAction = (options: { type: 'array', title: 'Confluence URL', description: - 'Paste your confluence url. Ensure it follows this format: https://{confluence+base+url}/display/{spacekey}/{page+title}', + 'Paste your confluence url. Ensure it follows this format: https://{confluence+base+url}/display/{spacekey}/{page+title} or https://{confluence+base+url}/spaces/{spacekey}/pages/1234567/{page+title} for Confluence Cloud', items: { type: 'string', default: 'Confluence URL', @@ -98,6 +98,7 @@ export const createConfluenceToMarkdownAction = (options: { const { spacekey, title, titleWithSpaces } = await createConfluenceVariables(url); // This calls confluence to get the page html and page id + ctx.logger.info(`Fetching the Confluence content for ${url}`); const getConfluenceDoc = await fetchConfluence( `/rest/api/content?title=${title}&spaceKey=${spacekey}&expand=body.export_view`, config, diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts new file mode 100644 index 0000000000..97a69935c8 --- /dev/null +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createConfluenceVariables } from './helpers'; + +describe('createConfluenceVariables', () => { + it('should return values for Confluence Url', async () => { + const url = 'https://confluence.example.com/display/SPACEKEY/Page+Title'; + const { spacekey, title, titleWithSpaces } = + await createConfluenceVariables(url); + + expect(spacekey).toEqual('SPACEKEY'); + expect(title).toEqual('Page+Title'); + expect(titleWithSpaces).toEqual('Page Title'); + }); + + it('should return values for Confluence Cloud Url', async () => { + const url = + 'https://example.atlassian.net/wiki/spaces/CLOUDSPACEKEY/pages/1234567/Cloud+Page+Title'; + + const { spacekey, title, titleWithSpaces } = + await createConfluenceVariables(url); + + expect(spacekey).toEqual('CLOUDSPACEKEY'); + expect(title).toEqual('Cloud+Page+Title'); + expect(titleWithSpaces).toEqual('Cloud Page Title'); + }); +}); diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts index c741f6f6c0..0fb7118676 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts @@ -51,10 +51,13 @@ export const readFileAsString = async (fileDir: string) => { export const fetchConfluence = async (relativeUrl: string, config: Config) => { const baseUrl = config.getString('confluence.baseUrl'); const token = config.getString('confluence.token'); - const response: Response = await fetch(`${baseUrl}${relativeUrl}`, { + const isCloud = config.getOptionalBoolean('confluence.isCloud') || false; + const authToken = isCloud ? `Basic ${token}` : `Bearer ${token}`; + const url = `${baseUrl}${relativeUrl}`; + const response: Response = await fetch(url, { method: 'GET', headers: { - Authorization: `Bearer ${token}`, + Authorization: authToken, }, }); if (!response.ok) { @@ -73,6 +76,8 @@ export const getAndWriteAttachments = async ( const productArr: string[][] = []; const baseUrl = config.getString('confluence.baseUrl'); const token = config.getString('confluence.token'); + const isCloud = config.getOptionalBoolean('confluence.isCloud') || false; + const authToken = isCloud ? `Basic ${token}` : `Bearer ${token}`; await Promise.all( await arr.results.map(async (result: Result) => { const downloadLink = result._links.download; @@ -80,11 +85,11 @@ export const getAndWriteAttachments = async ( if (result.metadata.mediaType !== 'application/gliffy+json') { productArr.push([result.title.replace(/ /g, '%20'), downloadTitle]); } - - const res = await fetch(`${baseUrl}${downloadLink}`, { + const url = `${baseUrl}${downloadLink}`; + const res = await fetch(url, { method: 'GET', headers: { - Authorization: `Bearer ${token}`, + Authorization: authToken, }, }); if (!res.ok) { @@ -117,12 +122,19 @@ export const createConfluenceVariables = async (url: string) => { let titleWithSpaces: string | undefined = ''; const params = new URL(url); if (params.pathname.split('/')[1] === 'display') { + // https://confluence.example.com/display/SPACEKEY/Page+Title spacekey = params.pathname.split('/')[2]; title = params.pathname.split('/')[3]; titleWithSpaces = title?.replace(/\+/g, ' '); return { spacekey, title, titleWithSpaces }; + } else if (params.pathname.split('/')[2] === 'spaces') { + // https://example.atlassian.net/wiki/spaces/SPACEKEY/pages/1234567/Page+Title + spacekey = params.pathname.split('/')[3]; + title = params.pathname.split('/')[6]; + titleWithSpaces = title?.replace(/\+/g, ' '); + return { spacekey, title, titleWithSpaces }; } throw new InputError( - 'The Url format for Confluence is incorrect. Acceptable format is `/display//`', + 'The Url format for Confluence is incorrect. Acceptable format is `/display//` or `/spaces//pages//` for Confluence cloud', ); }; diff --git a/yarn.lock b/yarn.lock index 07c79358be..f14d695cbd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8256,7 +8256,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-scaffolder-backend-module-confluence-to-markdown@workspace:plugins/scaffolder-backend-module-confluence-to-markdown": +"@backstage/plugin-scaffolder-backend-module-confluence-to-markdown@workspace:^, @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@workspace:plugins/scaffolder-backend-module-confluence-to-markdown": version: 0.0.0-use.local resolution: "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown@workspace:plugins/scaffolder-backend-module-confluence-to-markdown" dependencies: @@ -24409,6 +24409,7 @@ __metadata: "@backstage/plugin-proxy-backend": "workspace:^" "@backstage/plugin-rollbar-backend": "workspace:^" "@backstage/plugin-scaffolder-backend": "workspace:^" + "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "workspace:^" "@backstage/plugin-scaffolder-backend-module-rails": "workspace:^" "@backstage/plugin-search-backend": "workspace:^" "@backstage/plugin-search-backend-module-elasticsearch": "workspace:^" From 5762f99397d30960d4d278a9689d90c4ad76576c Mon Sep 17 00:00:00 2001 From: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Date: Thu, 20 Apr 2023 10:21:21 -0500 Subject: [PATCH 2/8] Added config schema Signed-off-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> --- .../README.md | 4 +-- .../config.d.ts | 34 +++++++++++++++++++ .../package.json | 6 ++-- 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 plugins/scaffolder-backend-module-confluence-to-markdown/config.d.ts diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/README.md b/plugins/scaffolder-backend-module-confluence-to-markdown/README.md index ee04f1650e..0fe9b43ed7 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/README.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/README.md @@ -79,11 +79,11 @@ confluence: isCloud: true ``` -##### baseUrl +##### Base URL The `baseUrl` for Confluence Cloud should include the product name which is `wiki` by default but can be something else if your Org has changed it. An example `baseUrl` for Confluence Cloud would look like this: `https://example.atlassian.net/wiki` -##### token +##### Token The `token` for Confluence Cloud needs to be base-64 encoded with your Atlassian account email address. Here's how to do that: diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/config.d.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/config.d.ts new file mode 100644 index 0000000000..c566a629a4 --- /dev/null +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/config.d.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Config { + /** Configuration options for the Confluence to Markdown action */ + confluence?: { + /** + * The base URL for accessing the Confluence API + */ + baseUrl: string; + /** + * The authentication token for accessing the Confluence API + * * @visibility secret + */ + token: string; + /** + * Flag to determine if you are on Confluence Cloud, optional + */ + isCloud?: string; + }; +} diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index aa8d0741c9..641017a5d8 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -44,6 +44,8 @@ "msw": "^1.0.0" }, "files": [ - "dist" - ] + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts" } From 1da141c6110eb4fd2993186e27a9281baf9bba6e Mon Sep 17 00:00:00 2001 From: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Date: Thu, 20 Apr 2023 10:57:28 -0500 Subject: [PATCH 3/8] Fixed test to match additional logger.info call Signed-off-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> --- .../src/actions/confluence/confluenceToMarkdown.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts index 136bfc521e..6d6a3ac757 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts @@ -155,7 +155,7 @@ describe('confluence:transform:markdown', () => { expect(logger.info).toHaveBeenCalledWith( `Fetching the mkdocs.yml catalog from https://notreal.github.com/space/backstage/mkdocs.yml`, ); - expect(logger.info).toHaveBeenCalledTimes(5); + expect(logger.info).toHaveBeenCalledTimes(6); expect(createWriteStream).toHaveBeenCalledTimes(1); expect(readFile).toHaveBeenCalledTimes(1); expect(writeFile).toHaveBeenCalledTimes(1); @@ -202,7 +202,7 @@ describe('confluence:transform:markdown', () => { expect(logger.info).toHaveBeenCalledWith( `Fetching the mkdocs.yml catalog from https://notreal.github.com/space/backstage/mkdocs.yml`, ); - expect(logger.info).toHaveBeenCalledTimes(5); + expect(logger.info).toHaveBeenCalledTimes(6); expect(createWriteStream).not.toHaveBeenCalled(); expect(readFile).toHaveBeenCalledTimes(1); From 8492db6cbceffe113da41e40892f9ccce3cd50c3 Mon Sep 17 00:00:00 2001 From: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Date: Thu, 11 May 2023 10:18:55 -0500 Subject: [PATCH 4/8] Large refactor based on feedback Signed-off-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> --- .../README.md | 66 +++++---- .../config.d.ts | 25 +++- .../confluence/confluenceToMarkdown.ts | 12 +- .../src/actions/confluence/helpers.test.ts | 128 +++++++++++++++++- .../src/actions/confluence/helpers.ts | 96 +++++++++++-- 5 files changed, 274 insertions(+), 53 deletions(-) diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/README.md b/plugins/scaffolder-backend-module-confluence-to-markdown/README.md index 0fe9b43ed7..108898d1a0 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/README.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/README.md @@ -60,38 +60,58 @@ export default async function createPlugin( ### Configuration -You will also need an access token for authorization with `Read` permissions. You can create a Personal Access Token (PAT) in confluence and add the PAT to your `app-config.yaml` +There is some configuration that needs to be setup to use this action, here are all the settings: ```yaml confluence: - baseUrl: ${CONFLUENCE_BASE_URL} - token: ${CONFLUENCE_TOKEN} + baseUrl: 'https://confluence.example.com' + auth: 'basic' + token: '${CONFLUENCE_TOKEN}' + email: 'example@company.org' + username: 'your-username' + password: 'your-password' ``` -#### Confluence Cloud - -For those using Confluence Cloud you will need to have the following configuration: - -```yaml -confluence: - baseUrl: ${CONFLUENCE_BASE_URL} - token: ${CONFLUENCE_TOKEN} - isCloud: true -``` - -##### Base URL +#### Base URL The `baseUrl` for Confluence Cloud should include the product name which is `wiki` by default but can be something else if your Org has changed it. An example `baseUrl` for Confluence Cloud would look like this: `https://example.atlassian.net/wiki` -##### Token +If you are using a self-hosted Confluence instance this does not apply to you. Your `baseUrl` would look something like this: `https://confluence.example.com` -The `token` for Confluence Cloud needs to be base-64 encoded with your Atlassian account email address. Here's how to do that: +#### Auth Methods -1. First get your token from: `https://.atlassian.com/manage-profile/security/api-tokens` -2. Next we need to setup a string in this format: `:` -3. For this example we'll use this: `confluence@backstage.io:wDzAzoXWRGLtvbgHvT0W` -4. Now we can run `echo -n "confluence@backstage.io:wDzAzoXWRGLtvbgHvT0W" | base64` -5. This gives us: `Y29uZmx1ZW5jZUBiYWNrc3RhZ2UuaW86d0R6QXpvWFdSR0x0dmJnSHZUMFc=` which we can now use as the value for the `token` in the configuration +The default authorization method is `basic` but `bearer` and `userpass` are also supported. Here's how you would configure each of these: + +For `basic`: + +```yaml +confluence: + baseUrl: 'https://confluence.example.com' + auth: 'basic' + token: '${CONFLUENCE_TOKEN}' +``` + +For `bearer`: + +```yaml +confluence: + baseUrl: 'https://confluence.example.com' + auth: 'bearer' + token: '${CONFLUENCE_TOKEN}' + email: 'example@company.org' +``` + +For `userpass` + +```yaml +confluence: + baseUrl: 'https://confluence.example.com' + auth: 'userpass' + username: 'your-username' + password: 'your-password' +``` + +**Note:** For `basic` and `bearer` authorization methods you will need an access token for authorization with `Read` permissions. You can create a Personal Access Token (PAT) in Confluence. The value used should be the raw token as it will be encoded for you by the action. ### Template Usage @@ -146,4 +166,4 @@ spec: Replace `` with your GitHub URL without `https://`. -You can find a list of all registered actions including their parameters at the /create/actions route in your Backstage application. +You can find a list of all registered actions including their parameters at the `/create/actions` route in your Backstage application. diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/config.d.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/config.d.ts index c566a629a4..9e3ba5df28 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/config.d.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/config.d.ts @@ -22,13 +22,28 @@ export interface Config { */ baseUrl: string; /** - * The authentication token for accessing the Confluence API - * * @visibility secret + * Authentication method - basic, bearer, username/password */ - token: string; + auth: 'basic' | 'bearer' | 'userpass'; /** - * Flag to determine if you are on Confluence Cloud, optional + * Token used for the basic and bearer auth methods + * @visibility secret */ - isCloud?: string; + token?: string; + /** + * Email encoded with the token for the bearer auth method + * @visibility secret + */ + email?: string; + /** + * Username used with the Username/Password auth method + * @visibility secret + */ + username?: string; + /** + * Password used with the Username/Password auth method + * @visibility secret + */ + password?: string; }; } diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts index 5e8553e426..67db15453e 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts @@ -28,6 +28,7 @@ import { fetchConfluence, getAndWriteAttachments, createConfluenceVariables, + getConfluenceConfig, } from './helpers'; /** @@ -57,7 +58,7 @@ export const createConfluenceToMarkdownAction = (options: { type: 'array', title: 'Confluence URL', description: - 'Paste your confluence url. Ensure it follows this format: https://{confluence+base+url}/display/{spacekey}/{page+title} or https://{confluence+base+url}/spaces/{spacekey}/pages/1234567/{page+title} for Confluence Cloud', + 'Paste your confluence url. Ensure it follows this format: https://{confluence+base+url}/display/{spacekey}/{page+title} or https://{confluence+base+url}/spaces/{spacekey}/pages/1234567/{page+title} for Confluence Cloud', items: { type: 'string', default: 'Confluence URL', @@ -73,6 +74,7 @@ export const createConfluenceToMarkdownAction = (options: { }, }, async handler(ctx) { + const confluenceConfig = getConfluenceConfig(config); const { confluenceUrls, repoUrl } = ctx.input; const parsedRepoUrl = parseGitUrl(repoUrl); const filePathToMkdocs = parsedRepoUrl.filepath.substring( @@ -96,12 +98,12 @@ export const createConfluenceToMarkdownAction = (options: { for (const url of confluenceUrls) { const { spacekey, title, titleWithSpaces } = - await createConfluenceVariables(url); + createConfluenceVariables(url); // This calls confluence to get the page html and page id ctx.logger.info(`Fetching the Confluence content for ${url}`); const getConfluenceDoc = await fetchConfluence( `/rest/api/content?title=${title}&spaceKey=${spacekey}&expand=body.export_view`, - config, + confluenceConfig, ); if (getConfluenceDoc.results.length === 0) { throw new InputError( @@ -111,7 +113,7 @@ export const createConfluenceToMarkdownAction = (options: { // This gets attachments for the confluence page if they exist const getDocAttachments = await fetchConfluence( `/rest/api/content/${getConfluenceDoc.results[0].id}/child/attachment`, - config, + confluenceConfig, ); if (getDocAttachments.results.length) { @@ -121,7 +123,7 @@ export const createConfluenceToMarkdownAction = (options: { productArray = await getAndWriteAttachments( getDocAttachments, dirPath, - config, + confluenceConfig, filePathToMkdocs, ); } diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts index 97a69935c8..4b737998ec 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts @@ -13,28 +13,142 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createConfluenceVariables } from './helpers'; +import { ConfigReader } from '@backstage/config'; +import { + createConfluenceVariables, + getAuthorizationHeaderValue, + getConfluenceConfig, +} from './helpers'; describe('createConfluenceVariables', () => { - it('should return values for Confluence Url', async () => { + it('should return values for Confluence Url', () => { const url = 'https://confluence.example.com/display/SPACEKEY/Page+Title'; - const { spacekey, title, titleWithSpaces } = - await createConfluenceVariables(url); + const { spacekey, title, titleWithSpaces } = createConfluenceVariables(url); expect(spacekey).toEqual('SPACEKEY'); expect(title).toEqual('Page+Title'); expect(titleWithSpaces).toEqual('Page Title'); }); - it('should return values for Confluence Cloud Url', async () => { + it('should return values for Confluence Cloud Url', () => { const url = 'https://example.atlassian.net/wiki/spaces/CLOUDSPACEKEY/pages/1234567/Cloud+Page+Title'; - const { spacekey, title, titleWithSpaces } = - await createConfluenceVariables(url); + const { spacekey, title, titleWithSpaces } = createConfluenceVariables(url); expect(spacekey).toEqual('CLOUDSPACEKEY'); expect(title).toEqual('Cloud+Page+Title'); expect(titleWithSpaces).toEqual('Cloud Page Title'); }); }); + +describe('getConfluenceConfig', () => { + it('should return validate basic Confluence config', async () => { + const config = new ConfigReader({ + confluence: { + baseUrl: 'https://example.atlassian.net', + auth: 'basic', + token: 'fake_token', + }, + }); + + const validated = getConfluenceConfig(config); + + expect(validated).toEqual({ + baseUrl: 'https://example.atlassian.net', + auth: 'basic', + token: 'fake_token', + email: undefined, + username: undefined, + password: undefined, + }); + }); + + it('should return validate bearer Confluence config', async () => { + const config = new ConfigReader({ + confluence: { + baseUrl: 'https://example.atlassian.net', + auth: 'bearer', + token: 'fake_token', + email: 'example@example.atlassian.net', + }, + }); + + const validated = getConfluenceConfig(config); + + expect(validated).toEqual({ + baseUrl: 'https://example.atlassian.net', + auth: 'bearer', + token: 'fake_token', + email: 'example@example.atlassian.net', + username: undefined, + password: undefined, + }); + }); + + it('should return validate userpass Confluence config', async () => { + const config = new ConfigReader({ + confluence: { + baseUrl: 'https://example.atlassian.net', + auth: 'userpass', + username: 'fake_user', + password: 'fake_password', + }, + }); + + const validated = getConfluenceConfig(config); + + expect(validated).toEqual({ + baseUrl: 'https://example.atlassian.net', + auth: 'userpass', + token: undefined, + email: undefined, + username: 'fake_user', + password: 'fake_password', + }); + }); +}); + +describe('getAuthorizationHeaderValue', () => { + it('should return basic auth header value', async () => { + const config = { + baseUrl: 'https://example.atlassian.net', + auth: 'basic', + token: 'fake_token', + }; + + const authHeaderValue = getAuthorizationHeaderValue(config); + + expect(authHeaderValue).toEqual('Basic fake_token'); + }); + + it('should return bearer auth header value', async () => { + const config = { + baseUrl: 'https://example.atlassian.net', + auth: 'bearer', + token: 'fake_token', + email: 'example@example.atlassian.net', + }; + + const authHeaderValue = getAuthorizationHeaderValue(config); + + // Note: this is fake and just the encoded result + expect(authHeaderValue).toEqual( + 'Bearer ZXhhbXBsZUBleGFtcGxlLmF0bGFzc2lhbi5uZXQ6ZmFrZV90b2tlbg==', + ); + }); + + it('should return userpass auth header value', async () => { + const config = { + baseUrl: 'https://example.atlassian.net', + auth: 'userpass', + username: 'fake_user', + password: 'fake_password', + }; + + const authHeaderValue = getAuthorizationHeaderValue(config); + + // Note: this is fake and just the encoded result + expect(authHeaderValue).toEqual('Basic ZmFrZV91c2VyOmZha2VfcGFzc3dvcmQ='); + }); +}); diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts index 0fb7118676..be34b3ca94 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts @@ -43,21 +43,93 @@ export interface Results { results: Result[]; } +export type ConfluenceConfig = { + baseUrl: string; + auth: string; + token?: string; + email?: string; + username?: string; + password?: string; +}; + +export const getConfluenceConfig = (config: Config) => { + const confluenceConfig: ConfluenceConfig = { + baseUrl: config.getString('confluence.baseUrl'), + auth: config.getOptionalString('confluence.auth') ?? 'basic', + token: config.getOptionalString('confluence.token'), + email: config.getOptionalString('confluence.email'), + username: config.getOptionalString('confluence.username'), + password: config.getOptionalString('confluence.password'), + }; + + if ( + (confluenceConfig.auth === 'basic' || confluenceConfig.auth === 'bearer') && + !confluenceConfig.token + ) { + throw new Error( + `No token provided for the configured '${confluenceConfig.auth}' auth method`, + ); + } + + if (confluenceConfig.auth === 'bearer' && !confluenceConfig.email) { + throw new Error( + `No email provided for the configured '${confluenceConfig.auth}' auth method`, + ); + } + + if ( + confluenceConfig.auth === 'userpass' && + (!confluenceConfig.username || !confluenceConfig.password) + ) { + throw new Error( + `No username/password provided for the configured '${confluenceConfig.auth}' auth method`, + ); + } + + return confluenceConfig; +}; + +export const getAuthorizationHeaderValue = (config: ConfluenceConfig) => { + let authHeaderValue: string = ''; + switch (config.auth) { + case 'basic': + authHeaderValue = `Basic ${config.token}`; + break; + case 'bearer': { + const buffer = Buffer.from(`${config.email}:${config.token}`, 'utf8'); + authHeaderValue = `Bearer ${buffer.toString('base64')}`; + break; + } + case 'userpass': { + const buffer = Buffer.from( + `${config.username}:${config.password}`, + 'utf8', + ); + authHeaderValue = `Basic ${buffer.toString('base64')}`; + break; + } + default: + throw new Error(`Unknown auth method '${config.auth}' provided`); + } + return authHeaderValue; +}; + export const readFileAsString = async (fileDir: string) => { const content = await fs.readFile(fileDir, 'utf-8'); return content.toString(); }; -export const fetchConfluence = async (relativeUrl: string, config: Config) => { - const baseUrl = config.getString('confluence.baseUrl'); - const token = config.getString('confluence.token'); - const isCloud = config.getOptionalBoolean('confluence.isCloud') || false; - const authToken = isCloud ? `Basic ${token}` : `Bearer ${token}`; +export const fetchConfluence = async ( + relativeUrl: string, + config: ConfluenceConfig, +) => { + const baseUrl = config.baseUrl; + const authHeaderValue = getAuthorizationHeaderValue(config); const url = `${baseUrl}${relativeUrl}`; const response: Response = await fetch(url, { method: 'GET', headers: { - Authorization: authToken, + Authorization: authHeaderValue, }, }); if (!response.ok) { @@ -70,14 +142,12 @@ export const fetchConfluence = async (relativeUrl: string, config: Config) => { export const getAndWriteAttachments = async ( arr: Results, workspace: string, - config: Config, + config: ConfluenceConfig, mkdocsDir: string, ) => { const productArr: string[][] = []; - const baseUrl = config.getString('confluence.baseUrl'); - const token = config.getString('confluence.token'); - const isCloud = config.getOptionalBoolean('confluence.isCloud') || false; - const authToken = isCloud ? `Basic ${token}` : `Bearer ${token}`; + const baseUrl = config.baseUrl; + const authHeaderValue = getAuthorizationHeaderValue(config); await Promise.all( await arr.results.map(async (result: Result) => { const downloadLink = result._links.download; @@ -89,7 +159,7 @@ export const getAndWriteAttachments = async ( const res = await fetch(url, { method: 'GET', headers: { - Authorization: authToken, + Authorization: authHeaderValue, }, }); if (!res.ok) { @@ -116,7 +186,7 @@ export const getAndWriteAttachments = async ( return productArr; }; -export const createConfluenceVariables = async (url: string) => { +export const createConfluenceVariables = (url: string) => { let spacekey: string | undefined = undefined; let title: string | undefined = undefined; let titleWithSpaces: string | undefined = ''; From 6ece5434ca0103e727a377aa0ceec05f59455716 Mon Sep 17 00:00:00 2001 From: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Date: Thu, 11 May 2023 10:39:56 -0500 Subject: [PATCH 5/8] Added support for URLs with prefix Signed-off-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> --- .../src/actions/confluence/helpers.test.ts | 10 ++++++++++ .../src/actions/confluence/helpers.ts | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts index 4b737998ec..5cfd679a5e 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts @@ -30,6 +30,16 @@ describe('createConfluenceVariables', () => { expect(titleWithSpaces).toEqual('Page Title'); }); + it('should return values for Confluence Url with prefix', () => { + const url = + 'https://confluence.example.com/prefix/display/PREFIXSPACEKEY/Prefix+Page+Title'; + const { spacekey, title, titleWithSpaces } = createConfluenceVariables(url); + + expect(spacekey).toEqual('PREFIXSPACEKEY'); + expect(title).toEqual('Prefix+Page+Title'); + expect(titleWithSpaces).toEqual('Prefix Page Title'); + }); + it('should return values for Confluence Cloud Url', () => { const url = 'https://example.atlassian.net/wiki/spaces/CLOUDSPACEKEY/pages/1234567/Cloud+Page+Title'; diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts index be34b3ca94..c85689e372 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts @@ -197,6 +197,12 @@ export const createConfluenceVariables = (url: string) => { title = params.pathname.split('/')[3]; titleWithSpaces = title?.replace(/\+/g, ' '); return { spacekey, title, titleWithSpaces }; + } else if (params.pathname.split('/')[2] === 'display') { + // https://confluence.example.com/prefix/display/SPACEKEY/Page+Title + spacekey = params.pathname.split('/')[3]; + title = params.pathname.split('/')[4]; + titleWithSpaces = title?.replace(/\+/g, ' '); + return { spacekey, title, titleWithSpaces }; } else if (params.pathname.split('/')[2] === 'spaces') { // https://example.atlassian.net/wiki/spaces/SPACEKEY/pages/1234567/Page+Title spacekey = params.pathname.split('/')[3]; From 7db2ad4c866f8919ca5fc163c3afd117718626b3 Mon Sep 17 00:00:00 2001 From: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Date: Thu, 11 May 2023 11:11:12 -0500 Subject: [PATCH 6/8] Fixed inverted basic and bearer Signed-off-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> --- .../README.md | 22 +++++----- .../docs/sample-template.yaml | 44 +++++++++++++++++++ .../src/actions/confluence/helpers.test.ts | 24 +++++----- .../src/actions/confluence/helpers.ts | 12 ++--- 4 files changed, 73 insertions(+), 29 deletions(-) create mode 100644 plugins/scaffolder-backend-module-confluence-to-markdown/docs/sample-template.yaml diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/README.md b/plugins/scaffolder-backend-module-confluence-to-markdown/README.md index 108898d1a0..0618f4cea1 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/README.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/README.md @@ -65,7 +65,7 @@ There is some configuration that needs to be setup to use this action, here are ```yaml confluence: baseUrl: 'https://confluence.example.com' - auth: 'basic' + auth: 'bearer' token: '${CONFLUENCE_TOKEN}' email: 'example@company.org' username: 'your-username' @@ -80,16 +80,7 @@ If you are using a self-hosted Confluence instance this does not apply to you. Y #### Auth Methods -The default authorization method is `basic` but `bearer` and `userpass` are also supported. Here's how you would configure each of these: - -For `basic`: - -```yaml -confluence: - baseUrl: 'https://confluence.example.com' - auth: 'basic' - token: '${CONFLUENCE_TOKEN}' -``` +The default authorization method is `bearer` but `basic` and `userpass` are also supported. Here's how you would configure each of these: For `bearer`: @@ -98,6 +89,15 @@ confluence: baseUrl: 'https://confluence.example.com' auth: 'bearer' token: '${CONFLUENCE_TOKEN}' +``` + +For `basic`: + +```yaml +confluence: + baseUrl: 'https://confluence.example.com' + auth: 'basic' + token: '${CONFLUENCE_TOKEN}' email: 'example@company.org' ``` diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/docs/sample-template.yaml b/plugins/scaffolder-backend-module-confluence-to-markdown/docs/sample-template.yaml new file mode 100644 index 0000000000..a78383e86b --- /dev/null +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/docs/sample-template.yaml @@ -0,0 +1,44 @@ +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + name: confluence-to-markdown + title: Confluence to Markdown + description: This template converts a single confluence document to Markdown for Techdocs and adds it to a given GitHub repo. + tags: + - do-not-use + - poc +spec: + owner: team-d + type: service + parameters: + - title: Confluence and Github Repo Information + properties: + confluenceUrls: + type: array + description: Urls for confluence doc to be converted to markdown. In format /display// or /spaces//pages// for Confluence cloud + items: + type: string + default: confluence url + ui:options: + addable: true + minItems: 1 + maxItems: 5 + repoUrl: + type: string + title: GitHub URL mkdocs.yaml link + description: The GitHub repo URL to your mkdocs.yaml file. Example + steps: + - id: create-docs + name: Get markdown file created and update markdown.yaml file + action: confluence:transform:markdown + input: + confluenceUrls: ${{ parameters.confluenceUrls }} + repoUrl: ${{ parameters.repoUrl }} + - id: publish + name: Publish PR to GitHub + action: publish:github:pull-request + input: + repoUrl: ?repo=${{ steps['create-docs'].output.repo }}&owner=${{ steps['create-docs'].output.owner }} + branchName: confluence-to-markdown + title: Confluence to Markdown + description: PR for converting confluence page to mkdocs \ No newline at end of file diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts index 5cfd679a5e..43d3b4b7dc 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts @@ -53,11 +53,11 @@ describe('createConfluenceVariables', () => { }); describe('getConfluenceConfig', () => { - it('should return validate basic Confluence config', async () => { + it('should return validate bearer Confluence config', async () => { const config = new ConfigReader({ confluence: { baseUrl: 'https://example.atlassian.net', - auth: 'basic', + auth: 'bearer', token: 'fake_token', }, }); @@ -66,7 +66,7 @@ describe('getConfluenceConfig', () => { expect(validated).toEqual({ baseUrl: 'https://example.atlassian.net', - auth: 'basic', + auth: 'bearer', token: 'fake_token', email: undefined, username: undefined, @@ -74,11 +74,11 @@ describe('getConfluenceConfig', () => { }); }); - it('should return validate bearer Confluence config', async () => { + it('should return validate basic Confluence config', async () => { const config = new ConfigReader({ confluence: { baseUrl: 'https://example.atlassian.net', - auth: 'bearer', + auth: 'basic', token: 'fake_token', email: 'example@example.atlassian.net', }, @@ -88,7 +88,7 @@ describe('getConfluenceConfig', () => { expect(validated).toEqual({ baseUrl: 'https://example.atlassian.net', - auth: 'bearer', + auth: 'basic', token: 'fake_token', email: 'example@example.atlassian.net', username: undefined, @@ -120,22 +120,22 @@ describe('getConfluenceConfig', () => { }); describe('getAuthorizationHeaderValue', () => { - it('should return basic auth header value', async () => { + it('should return bearer auth header value', async () => { const config = { baseUrl: 'https://example.atlassian.net', - auth: 'basic', + auth: 'bearer', token: 'fake_token', }; const authHeaderValue = getAuthorizationHeaderValue(config); - expect(authHeaderValue).toEqual('Basic fake_token'); + expect(authHeaderValue).toEqual('Bearer fake_token'); }); - it('should return bearer auth header value', async () => { + it('should return basic auth header value', async () => { const config = { baseUrl: 'https://example.atlassian.net', - auth: 'bearer', + auth: 'basic', token: 'fake_token', email: 'example@example.atlassian.net', }; @@ -144,7 +144,7 @@ describe('getAuthorizationHeaderValue', () => { // Note: this is fake and just the encoded result expect(authHeaderValue).toEqual( - 'Bearer ZXhhbXBsZUBleGFtcGxlLmF0bGFzc2lhbi5uZXQ6ZmFrZV90b2tlbg==', + 'Basic ZXhhbXBsZUBleGFtcGxlLmF0bGFzc2lhbi5uZXQ6ZmFrZV90b2tlbg==', ); }); diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts index c85689e372..6d290a6e8c 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts @@ -55,7 +55,7 @@ export type ConfluenceConfig = { export const getConfluenceConfig = (config: Config) => { const confluenceConfig: ConfluenceConfig = { baseUrl: config.getString('confluence.baseUrl'), - auth: config.getOptionalString('confluence.auth') ?? 'basic', + auth: config.getOptionalString('confluence.auth') ?? 'bearer', token: config.getOptionalString('confluence.token'), email: config.getOptionalString('confluence.email'), username: config.getOptionalString('confluence.username'), @@ -71,7 +71,7 @@ export const getConfluenceConfig = (config: Config) => { ); } - if (confluenceConfig.auth === 'bearer' && !confluenceConfig.email) { + if (confluenceConfig.auth === 'basic' && !confluenceConfig.email) { throw new Error( `No email provided for the configured '${confluenceConfig.auth}' auth method`, ); @@ -92,12 +92,12 @@ export const getConfluenceConfig = (config: Config) => { export const getAuthorizationHeaderValue = (config: ConfluenceConfig) => { let authHeaderValue: string = ''; switch (config.auth) { - case 'basic': - authHeaderValue = `Basic ${config.token}`; + case 'bearer': + authHeaderValue = `Bearer ${config.token}`; break; - case 'bearer': { + case 'basic': { const buffer = Buffer.from(`${config.email}:${config.token}`, 'utf8'); - authHeaderValue = `Bearer ${buffer.toString('base64')}`; + authHeaderValue = `Basic ${buffer.toString('base64')}`; break; } case 'userpass': { From 4d88eef69345865f776194f060e06264bfa15126 Mon Sep 17 00:00:00 2001 From: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Date: Mon, 15 May 2023 10:29:48 -0500 Subject: [PATCH 7/8] Formatted sample-template Signed-off-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> --- .../docs/sample-template.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/docs/sample-template.yaml b/plugins/scaffolder-backend-module-confluence-to-markdown/docs/sample-template.yaml index a78383e86b..3b37096db1 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/docs/sample-template.yaml +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/docs/sample-template.yaml @@ -41,4 +41,4 @@ spec: repoUrl: ?repo=${{ steps['create-docs'].output.repo }}&owner=${{ steps['create-docs'].output.owner }} branchName: confluence-to-markdown title: Confluence to Markdown - description: PR for converting confluence page to mkdocs \ No newline at end of file + description: PR for converting confluence page to mkdocs From 3a4795b0588e26287266432e2b79ee680f89607d Mon Sep 17 00:00:00 2001 From: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Date: Wed, 24 May 2023 12:46:40 -0500 Subject: [PATCH 8/8] Changes based on feedback Signed-off-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> --- .changeset/famous-days-kneel.md | 2 +- .../README.md | 15 ++++++--------- .../config.d.ts | 2 +- .../docs/sample-template.yaml | 7 +++---- .../actions/confluence/confluenceToMarkdown.ts | 2 +- .../src/actions/confluence/helpers.ts | 11 +++-------- 6 files changed, 15 insertions(+), 24 deletions(-) diff --git a/.changeset/famous-days-kneel.md b/.changeset/famous-days-kneel.md index 8aef0f4da8..418a1f6d4e 100644 --- a/.changeset/famous-days-kneel.md +++ b/.changeset/famous-days-kneel.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch --- -Added support for Confluence Cloud to the `confluence:transform:markdown` action, view the [README](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-confluence-to-markdown) for more details +Added support for Confluence Cloud to the `confluence:transform:markdown` action in addition to the existing Confluence Server support, view the [README](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-confluence-to-markdown) for more details diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/README.md b/plugins/scaffolder-backend-module-confluence-to-markdown/README.md index 0618f4cea1..7dd9bd96b1 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/README.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/README.md @@ -60,18 +60,16 @@ export default async function createPlugin( ### Configuration -There is some configuration that needs to be setup to use this action, here are all the settings: +There is some configuration that needs to be setup to use this action, these are the base parameters: ```yaml confluence: baseUrl: 'https://confluence.example.com' - auth: 'bearer' token: '${CONFLUENCE_TOKEN}' - email: 'example@company.org' - username: 'your-username' - password: 'your-password' ``` +The sections below will go into more details about the Base URL and Auth Methods. + #### Base URL The `baseUrl` for Confluence Cloud should include the product name which is `wiki` by default but can be something else if your Org has changed it. An example `baseUrl` for Confluence Cloud would look like this: `https://example.atlassian.net/wiki` @@ -123,7 +121,7 @@ kind: Template metadata: name: confluence-to-markdown title: Confluence to Markdown - description: This template converts a single confluence document to Markdown for Techdocs and adds it to a given GitHub repo. + description: This template converts a single Confluence document to Markdown for Techdocs and adds it to a given GitHub repo. tags: - do-not-use - poc @@ -135,10 +133,9 @@ spec: properties: confluenceUrls: type: array - description: Urls for confluence doc to be converted to markdown. In format /display// or /spaces//pages// for Confluence cloud + description: Urls for Confluence doc to be converted to markdown. In format /display// or /spaces//pages// for Confluence cloud items: type: string - default: confluence url ui:options: addable: true minItems: 1 @@ -161,7 +158,7 @@ spec: repoUrl: ?repo=${{ steps['create-docs'].output.repo }}&owner=${{ steps['create-docs'].output.owner }} branchName: confluence-to-markdown title: Confluence to Markdown - description: PR for converting confluence page to mkdocs + description: PR for converting Confluence page to mkdocs ``` Replace `` with your GitHub URL without `https://`. diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/config.d.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/config.d.ts index 9e3ba5df28..259d34cbf6 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/config.d.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/config.d.ts @@ -31,7 +31,7 @@ export interface Config { */ token?: string; /** - * Email encoded with the token for the bearer auth method + * Email used with the token for the basic auth method * @visibility secret */ email?: string; diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/docs/sample-template.yaml b/plugins/scaffolder-backend-module-confluence-to-markdown/docs/sample-template.yaml index 3b37096db1..bd708b915c 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/docs/sample-template.yaml +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/docs/sample-template.yaml @@ -3,7 +3,7 @@ kind: Template metadata: name: confluence-to-markdown title: Confluence to Markdown - description: This template converts a single confluence document to Markdown for Techdocs and adds it to a given GitHub repo. + description: This template converts a single Confluence document to Markdown for Techdocs and adds it to a given GitHub repo. tags: - do-not-use - poc @@ -15,10 +15,9 @@ spec: properties: confluenceUrls: type: array - description: Urls for confluence doc to be converted to markdown. In format /display// or /spaces//pages// for Confluence cloud + description: URLs for Confluence doc to be converted to markdown. In format /display// or /spaces//pages// for Confluence cloud items: type: string - default: confluence url ui:options: addable: true minItems: 1 @@ -41,4 +40,4 @@ spec: repoUrl: ?repo=${{ steps['create-docs'].output.repo }}&owner=${{ steps['create-docs'].output.owner }} branchName: confluence-to-markdown title: Confluence to Markdown - description: PR for converting confluence page to mkdocs + description: PR for converting Confluence page to mkdocs diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts index 67db15453e..9b666fd99e 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts @@ -58,7 +58,7 @@ export const createConfluenceToMarkdownAction = (options: { type: 'array', title: 'Confluence URL', description: - 'Paste your confluence url. Ensure it follows this format: https://{confluence+base+url}/display/{spacekey}/{page+title} or https://{confluence+base+url}/spaces/{spacekey}/pages/1234567/{page+title} for Confluence Cloud', + 'Paste your Confluence url. Ensure it follows this format: https://{confluence+base+url}/display/{spacekey}/{page+title} or https://{confluence+base+url}/spaces/{spacekey}/pages/1234567/{page+title} for Confluence Cloud', items: { type: 'string', default: 'Confluence URL', diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts index 6d290a6e8c..b2156a1103 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts @@ -90,28 +90,23 @@ export const getConfluenceConfig = (config: Config) => { }; export const getAuthorizationHeaderValue = (config: ConfluenceConfig) => { - let authHeaderValue: string = ''; switch (config.auth) { case 'bearer': - authHeaderValue = `Bearer ${config.token}`; - break; + return `Bearer ${config.token}`; case 'basic': { const buffer = Buffer.from(`${config.email}:${config.token}`, 'utf8'); - authHeaderValue = `Basic ${buffer.toString('base64')}`; - break; + return `Basic ${buffer.toString('base64')}`; } case 'userpass': { const buffer = Buffer.from( `${config.username}:${config.password}`, 'utf8', ); - authHeaderValue = `Basic ${buffer.toString('base64')}`; - break; + return `Basic ${buffer.toString('base64')}`; } default: throw new Error(`Unknown auth method '${config.auth}' provided`); } - return authHeaderValue; }; export const readFileAsString = async (fileDir: string) => {