diff --git a/.changeset/fluffy-lies-complain.md b/.changeset/fluffy-lies-complain.md index addffc2baa..b770928e1c 100644 --- a/.changeset/fluffy-lies-complain.md +++ b/.changeset/fluffy-lies-complain.md @@ -1,9 +1,38 @@ --- -'@backstage/techdocs-common': patch +'@backstage/techdocs-common': minor --- -Move the sanity checks of the publisher configurations to a dedicated `PublisherBase#validateConfiguration()` method instead of throwing an error when doing `Publisher.fromConfig(...)`. -If you want to preserve this check in your application, use the following code: +Move the sanity checks of the publisher configurations to a dedicated `PublisherBase#getReadiness()` method instead of throwing an error when doing `Publisher.fromConfig(...)`. +You should include the check when your backend to get early feedback about a potential misconfiguration: + +```diff + // packages/backend/src/plugins/techdocs.ts + + export default async function createPlugin({ + logger, + config, + discovery, + reader, + }: PluginEnvironment): Promise { + // ... + + const publisher = await Publisher.fromConfig(config, { + logger, + discovery, + }) + ++ // checks if the publisher is working and logs the result ++ await publisher.getReadiness(); + + // Docker client (conditionally) used by the generators, based on techdocs.generators config. + const dockerClient = new Docker(); + + // ... +} +``` + +If you want to crash your application on invalid configurations, you can throw an `Error` to preserve the old behavior. +Please be aware that this is not the recommended for the use in a Backstage backend but might be helpful in CLI tools such as the `techdocs-cli`. ```ts const publisher = await Publisher.fromConfig(config, { @@ -11,8 +40,8 @@ const publisher = await Publisher.fromConfig(config, { discovery, }); -const validation = await publisher.validateConfiguration(); -if (!validation.isValid) { +const ready = await publisher.getReadiness(); +if (!ready.isAvailable) { throw new Error('Invalid TechDocs publisher configuration'); } ``` diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts index 9883f2afbc..d0387cd15d 100644 --- a/packages/backend/src/plugins/techdocs.ts +++ b/packages/backend/src/plugins/techdocs.ts @@ -48,6 +48,9 @@ export default async function createPlugin({ discovery, }); + // checks if the publisher is working and logs the result + await publisher.getReadiness(); + // Docker client (conditionally) used by the generators, based on techdocs.generators config. const dockerClient = new Docker(); diff --git a/packages/create-app/templates/default-app/packages/backend/src/plugins/techdocs.ts b/packages/create-app/templates/default-app/packages/backend/src/plugins/techdocs.ts index 0ba08cefaf..231a7e7fd7 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/plugins/techdocs.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/plugins/techdocs.ts @@ -34,6 +34,9 @@ export default async function createPlugin({ discovery, }); + // checks if the publisher is working and logs the result + await publisher.getReadiness(); + // Docker client (conditionally) used by the generators, based on techdocs.generators config. const dockerClient = new Docker(); diff --git a/packages/techdocs-common/src/stages/publish/awsS3.test.ts b/packages/techdocs-common/src/stages/publish/awsS3.test.ts index b0000b6464..15da3b7cfc 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.test.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.test.ts @@ -85,10 +85,10 @@ beforeEach(() => { }); describe('AwsS3Publish', () => { - describe('validateConfiguration', () => { + describe('getReadiness', () => { it('should validate correct config', async () => { - expect(await publisher.validateConfiguration()).toEqual({ - isValid: true, + expect(await publisher.getReadiness()).toEqual({ + isAvailable: true, }); }); @@ -112,8 +112,8 @@ describe('AwsS3Publish', () => { const errorPublisher = AwsS3Publish.fromConfig(mockConfig, logger); - expect(await errorPublisher.validateConfiguration()).toEqual({ - isValid: false, + expect(await errorPublisher.getReadiness()).toEqual({ + isAvailable: false, }); }); }); diff --git a/packages/techdocs-common/src/stages/publish/awsS3.ts b/packages/techdocs-common/src/stages/publish/awsS3.ts index 77bb771fb6..9c6684fdca 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.ts @@ -27,9 +27,9 @@ import { Readable } from 'stream'; import { Logger } from 'winston'; import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers'; import { - ConfigurationValidationResponse, PublisherBase, PublishRequest, + ReadinessResponse, TechDocsMetadata, } from './types'; @@ -134,7 +134,7 @@ export class AwsS3Publish implements PublisherBase { * Check if the defined bucket exists. Being able to connect means the configuration is good * and the storage client will work. */ - async validateConfiguration(): Promise { + async getReadiness(): Promise { try { await this.storageClient .headBucket({ Bucket: this.bucketName }) @@ -144,7 +144,7 @@ export class AwsS3Publish implements PublisherBase { `Successfully connected to the AWS S3 bucket ${this.bucketName}.`, ); - return { isValid: true }; + return { isAvailable: true }; } catch (error) { this.logger.error( `Could not retrieve metadata about the AWS S3 bucket ${this.bucketName}. ` + @@ -154,7 +154,7 @@ export class AwsS3Publish implements PublisherBase { ); this.logger.error(`from AWS client library`, error); return { - isValid: false, + isAvailable: false, }; } } diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts index b67e47c675..c431e924a8 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts @@ -86,10 +86,10 @@ beforeEach(async () => { }); describe('publishing with valid credentials', () => { - describe('validateConfiguration', () => { + describe('getReadiness', () => { it('should validate correct config', async () => { - expect(await publisher.validateConfiguration()).toEqual({ - isValid: true, + expect(await publisher.getReadiness()).toEqual({ + isAvailable: true, }); }); @@ -115,8 +115,8 @@ describe('publishing with valid credentials', () => { logger, ); - expect(await errorPublisher.validateConfiguration()).toEqual({ - isValid: false, + expect(await errorPublisher.getReadiness()).toEqual({ + isAvailable: false, }); expect(logger.error).toHaveBeenCalledWith( diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts index 6ecc2b76cc..1a823c3d19 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts @@ -27,9 +27,9 @@ import { default as path, default as platformPath } from 'path'; import { Logger } from 'winston'; import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers'; import { - ConfigurationValidationResponse, PublisherBase, PublishRequest, + ReadinessResponse, TechDocsMetadata, } from './types'; @@ -93,7 +93,7 @@ export class AzureBlobStoragePublish implements PublisherBase { this.logger = logger; } - async validateConfiguration(): Promise { + async getReadiness(): Promise { try { const response = await this.storageClient .getContainerClient(this.containerName) @@ -101,7 +101,7 @@ export class AzureBlobStoragePublish implements PublisherBase { if (response._response.status === 200) { return { - isValid: true, + isAvailable: true, }; } @@ -121,7 +121,7 @@ export class AzureBlobStoragePublish implements PublisherBase { 'Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage', ); - return { isValid: false }; + return { isAvailable: false }; } /** diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts index c8cbcb0fda..7e580b16bc 100644 --- a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts @@ -83,10 +83,10 @@ beforeEach(async () => { }); describe('GoogleGCSPublish', () => { - describe('validateConfiguration', () => { + describe('getReadiness', () => { it('should validate correct config', async () => { - expect(await publisher.validateConfiguration()).toEqual({ - isValid: true, + expect(await publisher.getReadiness()).toEqual({ + isAvailable: true, }); }); @@ -106,8 +106,8 @@ describe('GoogleGCSPublish', () => { const errorPublisher = GoogleGCSPublish.fromConfig(mockConfig, logger); - expect(await errorPublisher.validateConfiguration()).toEqual({ - isValid: false, + expect(await errorPublisher.getReadiness()).toEqual({ + isAvailable: false, }); }); }); diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.ts b/packages/techdocs-common/src/stages/publish/googleStorage.ts index 047518d43f..28fbb42ece 100644 --- a/packages/techdocs-common/src/stages/publish/googleStorage.ts +++ b/packages/techdocs-common/src/stages/publish/googleStorage.ts @@ -27,9 +27,9 @@ import path from 'path'; import { Logger } from 'winston'; import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers'; import { - ConfigurationValidationResponse, PublisherBase, PublishRequest, + ReadinessResponse, TechDocsMetadata, } from './types'; @@ -84,7 +84,7 @@ export class GoogleGCSPublish implements PublisherBase { * Check if the defined bucket exists. Being able to connect means the configuration is good * and the storage client will work. */ - async validateConfiguration(): Promise { + async getReadiness(): Promise { try { await this.storageClient.bucket(this.bucketName).getMetadata(); this.logger.info( @@ -92,7 +92,7 @@ export class GoogleGCSPublish implements PublisherBase { ); return { - isValid: true, + isAvailable: true, }; } catch (err) { this.logger.error( @@ -103,7 +103,7 @@ export class GoogleGCSPublish implements PublisherBase { ); this.logger.error(`from GCS client library: ${err.message}`); - return { isValid: false }; + return { isAvailable: false }; } } diff --git a/packages/techdocs-common/src/stages/publish/local.ts b/packages/techdocs-common/src/stages/publish/local.ts index f641c63031..e09473cf60 100644 --- a/packages/techdocs-common/src/stages/publish/local.ts +++ b/packages/techdocs-common/src/stages/publish/local.ts @@ -25,10 +25,10 @@ import os from 'os'; import path from 'path'; import { Logger } from 'winston'; import { - ConfigurationValidationResponse, PublisherBase, PublishRequest, PublishResponse, + ReadinessResponse, TechDocsMetadata, } from './types'; @@ -66,9 +66,9 @@ export class LocalPublish implements PublisherBase { this.discovery = discovery; } - async validateConfiguration(): Promise { + async getReadiness(): Promise { return { - isValid: true, + isAvailable: true, }; } diff --git a/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts b/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts index 67de30245b..6f0aacede0 100644 --- a/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts +++ b/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts @@ -87,10 +87,10 @@ beforeEach(() => { }); describe('OpenStackSwiftPublish', () => { - describe('validateConfiguration', () => { + describe('getReadiness', () => { it('should validate correct config', async () => { - expect(await publisher.validateConfiguration()).toEqual({ - isValid: true, + expect(await publisher.getReadiness()).toEqual({ + isAvailable: true, }); }); @@ -118,8 +118,8 @@ describe('OpenStackSwiftPublish', () => { logger, ); - expect(await errorPublisher.validateConfiguration()).toEqual({ - isValid: false, + expect(await errorPublisher.getReadiness()).toEqual({ + isAvailable: false, }); }); }); diff --git a/packages/techdocs-common/src/stages/publish/openStackSwift.ts b/packages/techdocs-common/src/stages/publish/openStackSwift.ts index ca05cf2ce5..bff0055402 100644 --- a/packages/techdocs-common/src/stages/publish/openStackSwift.ts +++ b/packages/techdocs-common/src/stages/publish/openStackSwift.ts @@ -25,9 +25,9 @@ import { Readable } from 'stream'; import { Logger } from 'winston'; import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers'; import { - ConfigurationValidationResponse, PublisherBase, PublishRequest, + ReadinessResponse, TechDocsMetadata, } from './types'; @@ -92,7 +92,7 @@ export class OpenStackSwiftPublish implements PublisherBase { * Check if the defined container exists. Being able to connect means the configuration is good * and the storage client will work. */ - validateConfiguration(): Promise { + getReadiness(): Promise { return new Promise(resolve => { this.storageClient.getContainer(this.containerName, (err, container) => { if (container) { @@ -100,7 +100,7 @@ export class OpenStackSwiftPublish implements PublisherBase { `Successfully connected to the OpenStack Swift container ${this.containerName}.`, ); resolve({ - isValid: true, + isAvailable: true, }); } else { this.logger.error( @@ -112,7 +112,7 @@ export class OpenStackSwiftPublish implements PublisherBase { this.logger.error(`from OpenStack client library: ${err.message}`); resolve({ - isValid: false, + isAvailable: false, }); } }); diff --git a/packages/techdocs-common/src/stages/publish/types.ts b/packages/techdocs-common/src/stages/publish/types.ts index a4a02ae285..c2849b8e35 100644 --- a/packages/techdocs-common/src/stages/publish/types.ts +++ b/packages/techdocs-common/src/stages/publish/types.ts @@ -40,9 +40,9 @@ export type PublishResponse = { /** * Result for the validation check. */ -export type ConfigurationValidationResponse = { - /** Tells whether the configuration is valid. */ - isValid: boolean; +export type ReadinessResponse = { + /** If true, the publisher is able to interact with the backing storage. */ + isAvailable: boolean; }; /** @@ -62,12 +62,12 @@ export type TechDocsMetadata = { */ export interface PublisherBase { /** - * Check if the configuration is valid. This check tries to perform certain checks to see if the + * Check if the publisher is ready. This check tries to perform certain checks to see if the * publisher is configured correctly and can be used to publish or read documentations. * The different implementations might e.g. use the provided service credentials to access the * target or check if a folder/bucket is available. */ - validateConfiguration(): Promise; + getReadiness(): Promise; /** * Store the generated static files onto a storage service (either local filesystem or external service).