Skip to content

Commit

Permalink
feat(techdocs): Allow to pass options to GCS publisher (backstage#26836)
Browse files Browse the repository at this point in the history
* feat(techdocs): Allow to pass options to GCS publisher

Signed-off-by: Adrian Kosinski <[email protected]>

---------

Signed-off-by: Adrian Kosinski <[email protected]>
  • Loading branch information
adowsky authored Oct 1, 2024
1 parent 1b05958 commit fbdc631
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 9 deletions.
6 changes: 6 additions & 0 deletions .changeset/tough-fireants-itch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@backstage/plugin-techdocs-backend': patch
'@backstage/plugin-techdocs-node': patch
---

Allow to pass StorageOptions to GCS Publisher
30 changes: 30 additions & 0 deletions docs/features/techdocs/using-cloud-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,36 @@ techdocs:
Your Backstage app is now ready to use Google Cloud Storage for TechDocs, to
store and read the static generated documentation files.

### Extending default Storage configuration

If you need a non-standard configuration of Google Cloud Storage client,
`TechdocsPublisherExtensionPoint` is something you should look at.
You can register custom `StorageOptions` that will be used to configure the client. To do so, you
need to register publisher settings inside your module init, like in the following example:

```typescript
export const gcsPublisherCustomizer = createBackendModule({
pluginId: 'techdocs',
moduleId: 'gcs-publisher-customizer',
register(reg) {
reg.registerInit({
deps: {
techdocsExtensionPoint: techdocsPublisherExtensionPoint,
},
async init({ techdocsExtensionPoint }) {
const customOptions: StorageOptions = {
userAgent: 'my-custom-user-agent',
};
techdocsExtensionPoint.registerPublisherSettings(
'googleGcs',
customOptions,
);
},
});
},
});
```

## Configuring AWS S3 Bucket with TechDocs

**1. Set `techdocs.publisher.type` config in your `app-config.yaml`**
Expand Down
9 changes: 9 additions & 0 deletions plugins/techdocs-backend/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
Preparers,
Publisher,
PublisherBase,
PublisherSettings,
PublisherType,
RemoteProtocol,
techdocsBuildsExtensionPoint,
Expand Down Expand Up @@ -89,13 +90,20 @@ export const techdocsPlugin = createBackendPlugin({
});

let customTechdocsPublisher: PublisherBase | undefined;
const publisherSettings: PublisherSettings = {};
env.registerExtensionPoint(techdocsPublisherExtensionPoint, {
registerPublisher(type: PublisherType, publisher: PublisherBase) {
if (customTechdocsPublisher) {
throw new Error(`Publisher for type ${type} is already registered`);
}
customTechdocsPublisher = publisher;
},
registerPublisherSettings<T extends keyof PublisherSettings>(
publisher: T,
settings: PublisherSettings[T],
) {
publisherSettings[publisher] = settings;
},
});

env.registerInit({
Expand Down Expand Up @@ -144,6 +152,7 @@ export const techdocsPlugin = createBackendPlugin({
logger: winstonLogger,
discovery: discovery,
customPublisher: customTechdocsPublisher,
publisherSettings,
});

// checks if the publisher is working and logs the result
Expand Down
25 changes: 20 additions & 5 deletions plugins/techdocs-node/report.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { IndexableDocument } from '@backstage/plugin-search-common';
import { Logger } from 'winston';
import { LoggerService } from '@backstage/backend-plugin-api';
import { ScmIntegrationRegistry } from '@backstage/integration';
import { StorageOptions } from '@google-cloud/storage';
import { UrlReaderService } from '@backstage/backend-plugin-api';
import * as winston from 'winston';
import { Writable } from 'stream';
Expand Down Expand Up @@ -217,8 +218,15 @@ export type PublisherFactory = {
logger: LoggerService;
discovery: DiscoveryService;
customPublisher?: PublisherBase | undefined;
publisherSettings?: PublisherSettings;
};

// @public
export interface PublisherSettings {
// (undocumented)
googleGcs?: StorageOptions;
}

// @public
export type PublisherType =
| 'local'
Expand Down Expand Up @@ -344,6 +352,11 @@ export const techdocsPreparerExtensionPoint: ExtensionPoint<TechdocsPreparerExte
export interface TechdocsPublisherExtensionPoint {
// (undocumented)
registerPublisher(type: PublisherType, publisher: PublisherBase): void;
// (undocumented)
registerPublisherSettings<T extends keyof PublisherSettings>(
publisher: T,
settings: PublisherSettings[T],
): void;
}

// @public
Expand All @@ -368,13 +381,15 @@ export class UrlPreparer implements PreparerBase {

// Warnings were encountered during analysis:
//
// src/extensions.d.ts:10:5 - (ae-undocumented) Missing documentation for "setBuildStrategy".
// src/extensions.d.ts:11:5 - (ae-undocumented) Missing documentation for "setBuildLogTransport".
// src/extensions.d.ts:25:5 - (ae-undocumented) Missing documentation for "setTechdocsGenerator".
// src/extensions.d.ts:39:5 - (ae-undocumented) Missing documentation for "registerPreparer".
// src/extensions.d.ts:53:5 - (ae-undocumented) Missing documentation for "registerPublisher".
// src/extensions.d.ts:11:5 - (ae-undocumented) Missing documentation for "setBuildStrategy".
// src/extensions.d.ts:12:5 - (ae-undocumented) Missing documentation for "setBuildLogTransport".
// src/extensions.d.ts:26:5 - (ae-undocumented) Missing documentation for "setTechdocsGenerator".
// src/extensions.d.ts:40:5 - (ae-undocumented) Missing documentation for "registerPreparer".
// src/extensions.d.ts:54:5 - (ae-undocumented) Missing documentation for "registerPublisher".
// src/extensions.d.ts:55:5 - (ae-undocumented) Missing documentation for "registerPublisherSettings".
// src/stages/generate/index.d.ts:10:22 - (ae-undocumented) Missing documentation for "getMkDocsYml".
// src/stages/publish/publish.d.ts:10:5 - (ae-undocumented) Missing documentation for "register".
// src/stages/publish/publish.d.ts:11:5 - (ae-undocumented) Missing documentation for "get".
// src/stages/publish/types.d.ts:21:5 - (ae-undocumented) Missing documentation for "googleGcs".
// src/techdocsTypes.d.ts:39:5 - (ae-undocumented) Missing documentation for "shouldBuild".
```
5 changes: 5 additions & 0 deletions plugins/techdocs-node/src/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
TechdocsGenerator,
} from './stages';
import * as winston from 'winston';
import { PublisherSettings } from './stages/publish/types';

/**
* Extension point type for configuring TechDocs builds.
Expand Down Expand Up @@ -89,6 +90,10 @@ export const techdocsPreparerExtensionPoint =
*/
export interface TechdocsPublisherExtensionPoint {
registerPublisher(type: PublisherType, publisher: PublisherBase): void;
registerPublisherSettings<T extends keyof PublisherSettings>(
publisher: T,
settings: PublisherSettings[T],
): void;
}

/**
Expand Down
24 changes: 23 additions & 1 deletion plugins/techdocs-node/src/stages/publish/googleStorage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ import {
createMockDirectory,
mockServices,
} from '@backstage/backend-test-utils';
import { StorageOptions } from '@google-cloud/storage';

const mockDir = createMockDirectory();

let createdStorageOptions: Array<StorageOptions | undefined> = [];

jest.mock('@google-cloud/storage', () => {
class GCSFile {
constructor(private readonly filePath: string) {}
Expand Down Expand Up @@ -118,6 +121,10 @@ jest.mock('@google-cloud/storage', () => {
}

class Storage {
constructor(readonly options?: StorageOptions) {
createdStorageOptions.push(options);
}

bucket(bucketName: string) {
return new Bucket(bucketName);
}
Expand All @@ -144,10 +151,12 @@ const createPublisherFromConfig = ({
bucketName = 'bucketName',
bucketRootPath = '/',
legacyUseCaseSensitiveTripletPaths = false,
storageOptions = {},
}: {
bucketName?: string;
bucketRootPath?: string;
legacyUseCaseSensitiveTripletPaths?: boolean;
storageOptions?: StorageOptions;
} = {}) => {
const config = new ConfigReader({
techdocs: {
Expand All @@ -162,7 +171,7 @@ const createPublisherFromConfig = ({
legacyUseCaseSensitiveTripletPaths,
},
});
return GoogleGCSPublish.fromConfig(config, logger);
return GoogleGCSPublish.fromConfig(config, logger, storageOptions);
};

describe('GoogleGCSPublish', () => {
Expand Down Expand Up @@ -211,11 +220,24 @@ describe('GoogleGCSPublish', () => {
};

beforeEach(() => {
createdStorageOptions = [];
mockDir.setContent({
[directory]: files,
});
});

it('should pass options to storage', () => {
createPublisherFromConfig({
storageOptions: {
userAgent: 'Test-UA',
},
});

expect(createdStorageOptions.map(opt => opt?.userAgent)).toContain(
'Test-UA',
);
});

describe('getReadiness', () => {
it('should validate correct config', async () => {
const publisher = createPublisherFromConfig();
Expand Down
8 changes: 6 additions & 2 deletions plugins/techdocs-node/src/stages/publish/googleStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ export class GoogleGCSPublish implements PublisherBase {
this.bucketRootPath = options.bucketRootPath;
}

static fromConfig(config: Config, logger: LoggerService): PublisherBase {
static fromConfig(
config: Config,
logger: LoggerService,
options?: StorageOptions,
): PublisherBase {
let bucketName = '';
try {
bucketName = config.getString('techdocs.publisher.googleGcs.bucketName');
Expand Down Expand Up @@ -103,7 +107,7 @@ export class GoogleGCSPublish implements PublisherBase {
}
}

const clientOpts: StorageOptions = {};
const clientOpts: StorageOptions = options ?? {};
if (projectId) {
clientOpts.projectId = projectId;
}
Expand Down
1 change: 1 addition & 0 deletions plugins/techdocs-node/src/stages/publish/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ export type {
MigrateRequest,
ReadinessResponse,
TechDocsMetadata,
PublisherSettings,
} from './types';
6 changes: 5 additions & 1 deletion plugins/techdocs-node/src/stages/publish/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ export class Publisher implements PublisherBuilder {
logger.info('Creating Google Storage Bucket publisher for TechDocs');
publishers.register(
publisherType,
GoogleGCSPublish.fromConfig(config, logger),
GoogleGCSPublish.fromConfig(
config,
logger,
options.publisherSettings?.googleGcs,
),
);
break;
case 'awsS3':
Expand Down
10 changes: 10 additions & 0 deletions plugins/techdocs-node/src/stages/publish/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import express from 'express';
import { Config } from '@backstage/config';
import { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api';
import { Entity, CompoundEntityRef } from '@backstage/catalog-model';
import { StorageOptions } from '@google-cloud/storage';

/**
* Options for building publishers
Expand All @@ -26,8 +27,17 @@ export type PublisherFactory = {
logger: LoggerService;
discovery: DiscoveryService;
customPublisher?: PublisherBase | undefined;
publisherSettings?: PublisherSettings;
};

/**
* Additional configurations for publishers.
* @public
*/
export interface PublisherSettings {
googleGcs?: StorageOptions;
}

/**
* Key for all the different types of TechDocs publishers that are supported.
* @public
Expand Down

0 comments on commit fbdc631

Please sign in to comment.