Merge pull request #17468 from awanlin/topic/support-confluence-cloud
Added support for Confluence Cloud
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch
|
||||
---
|
||||
|
||||
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
|
||||
@@ -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:^",
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,15 +58,62 @@ export default async function createPlugin(
|
||||
}
|
||||
```
|
||||
|
||||
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`
|
||||
### Configuration
|
||||
|
||||
There is some configuration that needs to be setup to use this action, these are the base parameters:
|
||||
|
||||
```yaml
|
||||
confluence:
|
||||
baseUrl: ${CONFLUENCE_BASE_URL}
|
||||
token: ${CONFLUENCE_TOKEN}
|
||||
baseUrl: 'https://confluence.example.com'
|
||||
token: '${CONFLUENCE_TOKEN}'
|
||||
```
|
||||
|
||||
After that you can use the action in your template:
|
||||
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`
|
||||
|
||||
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`
|
||||
|
||||
#### Auth Methods
|
||||
|
||||
The default authorization method is `bearer` but `basic` and `userpass` are also supported. Here's how you would configure each of these:
|
||||
|
||||
For `bearer`:
|
||||
|
||||
```yaml
|
||||
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'
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
Here's an example of how you can use the action in your template:
|
||||
|
||||
```yaml
|
||||
apiVersion: scaffolder.backstage.io/v1beta3
|
||||
@@ -72,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
|
||||
@@ -84,10 +133,9 @@ spec:
|
||||
properties:
|
||||
confluenceUrls:
|
||||
type: array
|
||||
description: Urls for confluence doc to be converted to markdown. In format <CONFLUENCE_BASE_URL>/display/<SPACEKEY>/<PAGE+TITLE>
|
||||
description: Urls for Confluence doc to be converted to markdown. In format <CONFLUENCE_BASE_URL>/display/<SPACEKEY>/<PAGE+TITLE> or <CONFLUENCE_BASE_URL>/spaces/<SPACEKEY>/pages/<PAGEID>/<PAGE+TITLE> for Confluence cloud
|
||||
items:
|
||||
type: string
|
||||
default: confluence url
|
||||
ui:options:
|
||||
addable: true
|
||||
minItems: 1
|
||||
@@ -110,9 +158,9 @@ spec:
|
||||
repoUrl: <GITHUB_BASE_URL>?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 `<GITHUB_BASE_URL>` 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.
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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;
|
||||
/**
|
||||
* Authentication method - basic, bearer, username/password
|
||||
*/
|
||||
auth: 'basic' | 'bearer' | 'userpass';
|
||||
/**
|
||||
* Token used for the basic and bearer auth methods
|
||||
* @visibility secret
|
||||
*/
|
||||
token?: string;
|
||||
/**
|
||||
* Email used with the token for the basic 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;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
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 <CONFLUENCE_BASE_URL>/display/<SPACEKEY>/<PAGE+TITLE> or <CONFLUENCE_BASE_URL>/spaces/<SPACEKEY>/pages/<PAGEID>/<PAGE+TITLE> for Confluence cloud
|
||||
items:
|
||||
type: string
|
||||
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 <https://github.com/blob/master/mkdocs.yml>
|
||||
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: <GITHUB_BASE_URL>?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
|
||||
@@ -44,6 +44,8 @@
|
||||
"msw": "^1.0.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
"dist",
|
||||
"config.d.ts"
|
||||
],
|
||||
"configSchema": "config.d.ts"
|
||||
}
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
+8
-5
@@ -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}',
|
||||
'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,11 +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(
|
||||
@@ -110,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) {
|
||||
@@ -120,7 +123,7 @@ export const createConfluenceToMarkdownAction = (options: {
|
||||
productArray = await getAndWriteAttachments(
|
||||
getDocAttachments,
|
||||
dirPath,
|
||||
config,
|
||||
confluenceConfig,
|
||||
filePathToMkdocs,
|
||||
);
|
||||
}
|
||||
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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 { ConfigReader } from '@backstage/config';
|
||||
import {
|
||||
createConfluenceVariables,
|
||||
getAuthorizationHeaderValue,
|
||||
getConfluenceConfig,
|
||||
} from './helpers';
|
||||
|
||||
describe('createConfluenceVariables', () => {
|
||||
it('should return values for Confluence Url', () => {
|
||||
const url = 'https://confluence.example.com/display/SPACEKEY/Page+Title';
|
||||
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 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';
|
||||
|
||||
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 bearer Confluence config', async () => {
|
||||
const config = new ConfigReader({
|
||||
confluence: {
|
||||
baseUrl: 'https://example.atlassian.net',
|
||||
auth: 'bearer',
|
||||
token: 'fake_token',
|
||||
},
|
||||
});
|
||||
|
||||
const validated = getConfluenceConfig(config);
|
||||
|
||||
expect(validated).toEqual({
|
||||
baseUrl: 'https://example.atlassian.net',
|
||||
auth: 'bearer',
|
||||
token: 'fake_token',
|
||||
email: undefined,
|
||||
username: undefined,
|
||||
password: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return validate basic Confluence config', async () => {
|
||||
const config = new ConfigReader({
|
||||
confluence: {
|
||||
baseUrl: 'https://example.atlassian.net',
|
||||
auth: 'basic',
|
||||
token: 'fake_token',
|
||||
email: 'example@example.atlassian.net',
|
||||
},
|
||||
});
|
||||
|
||||
const validated = getConfluenceConfig(config);
|
||||
|
||||
expect(validated).toEqual({
|
||||
baseUrl: 'https://example.atlassian.net',
|
||||
auth: 'basic',
|
||||
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 bearer auth header value', async () => {
|
||||
const config = {
|
||||
baseUrl: 'https://example.atlassian.net',
|
||||
auth: 'bearer',
|
||||
token: 'fake_token',
|
||||
};
|
||||
|
||||
const authHeaderValue = getAuthorizationHeaderValue(config);
|
||||
|
||||
expect(authHeaderValue).toEqual('Bearer fake_token');
|
||||
});
|
||||
|
||||
it('should return basic auth header value', async () => {
|
||||
const config = {
|
||||
baseUrl: 'https://example.atlassian.net',
|
||||
auth: 'basic',
|
||||
token: 'fake_token',
|
||||
email: 'example@example.atlassian.net',
|
||||
};
|
||||
|
||||
const authHeaderValue = getAuthorizationHeaderValue(config);
|
||||
|
||||
// Note: this is fake and just the encoded result
|
||||
expect(authHeaderValue).toEqual(
|
||||
'Basic 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=');
|
||||
});
|
||||
});
|
||||
+96
-13
@@ -43,18 +43,88 @@ 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') ?? 'bearer',
|
||||
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 === 'basic' && !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) => {
|
||||
switch (config.auth) {
|
||||
case 'bearer':
|
||||
return `Bearer ${config.token}`;
|
||||
case 'basic': {
|
||||
const buffer = Buffer.from(`${config.email}:${config.token}`, 'utf8');
|
||||
return `Basic ${buffer.toString('base64')}`;
|
||||
}
|
||||
case 'userpass': {
|
||||
const buffer = Buffer.from(
|
||||
`${config.username}:${config.password}`,
|
||||
'utf8',
|
||||
);
|
||||
return `Basic ${buffer.toString('base64')}`;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown auth method '${config.auth}' provided`);
|
||||
}
|
||||
};
|
||||
|
||||
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 response: Response = await fetch(`${baseUrl}${relativeUrl}`, {
|
||||
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: `Bearer ${token}`,
|
||||
Authorization: authHeaderValue,
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
@@ -67,12 +137,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 baseUrl = config.baseUrl;
|
||||
const authHeaderValue = getAuthorizationHeaderValue(config);
|
||||
await Promise.all(
|
||||
await arr.results.map(async (result: Result) => {
|
||||
const downloadLink = result._links.download;
|
||||
@@ -80,11 +150,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: authHeaderValue,
|
||||
},
|
||||
});
|
||||
if (!res.ok) {
|
||||
@@ -111,18 +181,31 @@ 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 = '';
|
||||
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] === '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];
|
||||
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 `<CONFLUENCE_BASE_URL>/display/<SPACEKEY>/<PAGE+TITLE>`',
|
||||
'The Url format for Confluence is incorrect. Acceptable format is `<CONFLUENCE_BASE_URL>/display/<SPACEKEY>/<PAGE+TITLE>` or `<CONFLUENCE_BASE_URL>/spaces/<SPACEKEY>/pages/<PAGEID>/<PAGE+TITLE>` for Confluence cloud',
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8292,7 +8292,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:
|
||||
@@ -24503,6 +24503,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:^"
|
||||
|
||||
Reference in New Issue
Block a user