From cec1cd45786f0b3d4b822b28860d7ec2915f5420 Mon Sep 17 00:00:00 2001 From: ivgo Date: Thu, 16 Jun 2022 09:44:29 +0200 Subject: [PATCH 1/3] Add requested improvements to vault plugin Signed-off-by: ivgo --- plugins/vault-backend/api-report.md | 19 +-------- plugins/vault-backend/config.d.ts | 1 + plugins/vault-backend/package.json | 2 +- .../src/service/VaultBuilder.tsx | 21 +--------- .../src/service/vaultApi.test.ts | 3 +- plugins/vault-backend/src/service/vaultApi.ts | 38 +++++++---------- plugins/vault/api-report.md | 25 +++++++++++ plugins/vault/package.json | 1 + plugins/vault/src/api.test.ts | 42 ++++++++++--------- plugins/vault/src/api.ts | 40 +++++++++++++----- .../EntityVaultTable.test.tsx | 26 ++++++------ .../EntityVaultTable/EntityVaultTable.tsx | 21 ++++++---- plugins/vault/src/conditions.ts | 6 +++ plugins/vault/src/constants.ts | 4 ++ plugins/vault/src/index.ts | 4 ++ yarn.lock | 41 +++--------------- 16 files changed, 147 insertions(+), 147 deletions(-) diff --git a/plugins/vault-backend/api-report.md b/plugins/vault-backend/api-report.md index 1c7e669ef2..9f738b15e9 100644 --- a/plugins/vault-backend/api-report.md +++ b/plugins/vault-backend/api-report.md @@ -12,13 +12,6 @@ import { TaskRunner } from '@backstage/backend-tasks'; // @public export function createRouter(options: RouterOptions): express.Router; -// @public -export type RenewTokenResponse = { - auth: { - client_token: string; - }; -}; - // @public export interface RouterOptions { // (undocumented) @@ -33,7 +26,7 @@ export interface RouterOptions { export interface VaultApi { getFrontendSecretsUrl(): string; listSecrets(secretPath: string): Promise; - renewToken?(): Promise; + renewToken?(): Promise; } // @public @@ -45,7 +38,6 @@ export class VaultBuilder { enableTokenRenew(schedule?: TaskRunner): Promise; // (undocumented) protected readonly env: VaultEnvironment; - protected renewToken(vaultClient: VaultClient): Promise; setVaultClient(vaultClient: VaultClient): this; } @@ -62,7 +54,7 @@ export class VaultClient implements VaultApi { // (undocumented) listSecrets(secretPath: string): Promise; // (undocumented) - renewToken(): Promise; + renewToken(): Promise; } // @public @@ -82,12 +74,5 @@ export type VaultSecret = { editUrl: string; }; -// @public -export type VaultSecretList = { - data: { - keys: string[]; - }; -}; - // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/vault-backend/config.d.ts b/plugins/vault-backend/config.d.ts index b1f917463e..c7aac4db9d 100644 --- a/plugins/vault-backend/config.d.ts +++ b/plugins/vault-backend/config.d.ts @@ -19,6 +19,7 @@ export interface Config { vault?: { /** * The baseUrl for your Vault instance. + * @visibility frontend */ baseUrl: string; diff --git a/plugins/vault-backend/package.json b/plugins/vault-backend/package.json index 651b95f98c..080398ea52 100644 --- a/plugins/vault-backend/package.json +++ b/plugins/vault-backend/package.json @@ -54,7 +54,7 @@ "@types/compression": "^1.7.2", "@types/supertest": "^2.0.8", "msw": "^0.42.0", - "supertest": "^4.0.2" + "supertest": "^6.1.6" }, "files": [ "dist", diff --git a/plugins/vault-backend/src/service/VaultBuilder.tsx b/plugins/vault-backend/src/service/VaultBuilder.tsx index aa2858345f..67260f45ca 100644 --- a/plugins/vault-backend/src/service/VaultBuilder.tsx +++ b/plugins/vault-backend/src/service/VaultBuilder.tsx @@ -45,9 +45,6 @@ export type VaultBuilderReturn = { * @public */ export class VaultBuilder { - // private vaultTokenRefreshInterval: Duration = Duration.fromObject({ - // minutes: 60, - // }); private vaultClient?: VaultClient; /** @@ -118,26 +115,12 @@ export class VaultBuilder { fn: async () => { this.env.logger.info('Renewing Vault token'); const vaultClient = this.vaultClient ?? new VaultClient(this.env); - await this.renewToken(vaultClient); + await vaultClient.renewToken(); }, }); return this; } - /** - * Renews the token for vault using a defined client. - * - * @param vaultClient - The vault client used to renew the token - */ - protected async renewToken(vaultClient: VaultClient) { - const result = await vaultClient.renewToken(); - if (!result) { - this.env.logger.warn('Error renewing vault token'); - } else { - this.env.logger.info('Vault token renewed'); - } - } - /** * Builds the backend routes for Vault. * @@ -159,7 +142,7 @@ export class VaultBuilder { } const secrets = await vaultClient.listSecrets(path); - res.json(secrets); + res.json({ items: secrets }); }); return router; diff --git a/plugins/vault-backend/src/service/vaultApi.test.ts b/plugins/vault-backend/src/service/vaultApi.test.ts index 5a29633933..e80703c99f 100644 --- a/plugins/vault-backend/src/service/vaultApi.test.ts +++ b/plugins/vault-backend/src/service/vaultApi.test.ts @@ -93,8 +93,7 @@ describe('VaultApi', () => { it('should return success token renew', async () => { setupHandlers(); const api = new VaultClient({ config }); - const apiRenew = await api.renewToken(); - expect(apiRenew).toBeTruthy(); + expect(await api.renewToken()).toBe(undefined); }); it('should render frontend url', () => { diff --git a/plugins/vault-backend/src/service/vaultApi.ts b/plugins/vault-backend/src/service/vaultApi.ts index 0623780654..ad399dc6ac 100644 --- a/plugins/vault-backend/src/service/vaultApi.ts +++ b/plugins/vault-backend/src/service/vaultApi.ts @@ -15,12 +15,13 @@ */ import { Config } from '@backstage/config'; +import { NotFoundError } from '@backstage/errors'; import fetch from 'cross-fetch'; import { getVaultConfig, VaultConfig } from '../config'; /** * Object received as a response from the Vault API when fetching secrets - * @public + * @internal */ export type VaultSecretList = { data: { @@ -40,9 +41,8 @@ export type VaultSecret = { /** * Object received as response when the token is renewed using the Vault API - * @public */ -export type RenewTokenResponse = { +type RenewTokenResponse = { auth: { client_token: string; }; @@ -63,10 +63,10 @@ export interface VaultApi { */ listSecrets(secretPath: string): Promise; /** - * Optional, to renew the token used to list the secrets. Returns true - * if the action was successfull, false otherwise. + * Optional, to renew the token used to list the secrets. Throws an + * error if the token renewal went wrong. */ - renewToken?(): Promise; + renewToken?(): Promise; } /** @@ -84,7 +84,7 @@ export class VaultClient implements VaultApi { path: string, query: { [key in string]: any }, method: string = 'GET', - ): Promise { + ): Promise { const url = new URL(path, this.vaultConfig.baseUrl); const response = await fetch( `${url.toString()}?${new URLSearchParams(query).toString()}`, @@ -96,15 +96,14 @@ export class VaultClient implements VaultApi { }, }, ); - if (response.status === 200) { + if (response.ok) { return (await response.json()) as T; + } else if (response.status === 404) { + throw new NotFoundError(`No secrets found in path '${path}'`); } - return undefined; - } - - private isFolder(secretName: string): boolean { - const regex = /^.*\/$/gm; - return regex.test(secretName); + throw new Error( + `Unexpected error while fetching secrets from path '${path}'`, + ); } getFrontendSecretsUrl(): string { @@ -117,14 +116,11 @@ export class VaultClient implements VaultApi { ? `v1/${this.vaultConfig.secretEngine}/metadata/${secretPath}` : `v1/${this.vaultConfig.secretEngine}/${secretPath}`; const result = await this.callApi(listUrl, { list: true }); - if (!result) { - return []; - } const secrets: VaultSecret[] = []; await Promise.all( result.data.keys.map(async secret => { - if (this.isFolder(secret)) { + if (secret.endsWith('/')) { secrets.push( ...(await this.listSecrets(`${secretPath}/${secret.slice(0, -1)}`)), ); @@ -141,17 +137,13 @@ export class VaultClient implements VaultApi { return secrets; } - async renewToken(): Promise { + async renewToken(): Promise { const result = await this.callApi( 'v1/auth/token/renew-self', {}, 'POST', ); - if (!result) { - return false; - } this.vaultConfig.token = result.auth.client_token; - return true; } } diff --git a/plugins/vault/api-report.md b/plugins/vault/api-report.md index a0b5773c70..215ee274ec 100644 --- a/plugins/vault/api-report.md +++ b/plugins/vault/api-report.md @@ -5,13 +5,38 @@ ```ts /// +import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { Entity } from '@backstage/catalog-model'; // @public export const EntityVaultCard: () => JSX.Element; +// Warning: (ae-missing-release-tag) "isVaultAvailable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export function isVaultAvailable(entity: Entity): boolean; + +// @public (undocumented) +export const VAULT_SECRET_PATH_ANNOTATION = 'vault.io/secrets-path'; + +// @public +export interface VaultApi { + listSecrets(secretPath: string): Promise; +} + +// @public (undocumented) +export const vaultApiRef: ApiRef; + // @public export const vaultPlugin: BackstagePlugin<{}, {}>; +// @public +export type VaultSecret = { + name: string; + showUrl: string; + editUrl: string; +}; + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/vault/package.json b/plugins/vault/package.json index 54fc7cfc90..c4b447eaca 100644 --- a/plugins/vault/package.json +++ b/plugins/vault/package.json @@ -37,6 +37,7 @@ "@backstage/catalog-model": "^1.0.3", "@backstage/core-components": "^0.9.5", "@backstage/core-plugin-api": "^1.0.3", + "@backstage/errors": "^1.0.0", "@backstage/plugin-catalog-react": "^1.1.1", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", diff --git a/plugins/vault/src/api.test.ts b/plugins/vault/src/api.test.ts index 70c6a553df..3733d8be0f 100644 --- a/plugins/vault/src/api.test.ts +++ b/plugins/vault/src/api.test.ts @@ -27,18 +27,20 @@ describe('api', () => { const mockBaseUrl = 'https://api-vault.com/api/vault'; const discoveryApi = UrlPatternDiscovery.compile(mockBaseUrl); - const mockSecretsResult: VaultSecret[] = [ - { - name: 'secret::one', - editUrl: `${mockBaseUrl}/ui/vault/secrets/secrets/edit/test/success/secret::one`, - showUrl: `${mockBaseUrl}/ui/vault/secrets/secrets/show/test/success/secret::one`, - }, - { - name: 'secret::two', - editUrl: `${mockBaseUrl}/ui/vault/secrets/secrets/edit/test/success/secret::two`, - showUrl: `${mockBaseUrl}/ui/vault/secrets/secrets/show/test/success/secret::two`, - }, - ]; + const mockSecretsResult: { items: VaultSecret[] } = { + items: [ + { + name: 'secret::one', + editUrl: `${mockBaseUrl}/ui/vault/secrets/secrets/edit/test/success/secret::one`, + showUrl: `${mockBaseUrl}/ui/vault/secrets/secrets/show/test/success/secret::one`, + }, + { + name: 'secret::two', + editUrl: `${mockBaseUrl}/ui/vault/secrets/secrets/edit/test/success/secret::two`, + showUrl: `${mockBaseUrl}/ui/vault/secrets/secrets/show/test/success/secret::two`, + }, + ], + }; const setupHandlers = () => { server.use( @@ -47,31 +49,33 @@ describe('api', () => { if (path === 'test/success') { return res(ctx.json(mockSecretsResult)); } else if (path === 'test/error') { - return res(ctx.json([])); + return res(ctx.json({ items: [] })); } return res(ctx.status(400)); }), + rest.get(`${mockBaseUrl}/v1/secrets/`, (_req, res, ctx) => { + return res(ctx.json(mockSecretsResult)); + }), ); }; it('should return secrets', async () => { setupHandlers(); const api = new VaultClient({ discoveryApi }); - const secrets = await api.listSecrets('test/success'); - expect(secrets).toEqual(mockSecretsResult); + expect(await api.listSecrets('test/success')).toEqual( + mockSecretsResult.items, + ); }); it('should return empty secret list', async () => { setupHandlers(); const api = new VaultClient({ discoveryApi }); expect(await api.listSecrets('test/error')).toEqual([]); - expect(await api.listSecrets('')).toEqual([]); }); - it('should return no secrets', async () => { + it('should return all the secrets if no path defined', async () => { setupHandlers(); const api = new VaultClient({ discoveryApi }); - const secrets = await api.listSecrets(''); - expect(secrets).toEqual([]); + expect(await api.listSecrets('')).toEqual(mockSecretsResult.items); }); }); diff --git a/plugins/vault/src/api.ts b/plugins/vault/src/api.ts index 74b212429f..6c17e34d9a 100644 --- a/plugins/vault/src/api.ts +++ b/plugins/vault/src/api.ts @@ -14,21 +14,41 @@ * limitations under the License. */ import { DiscoveryApi, createApiRef } from '@backstage/core-plugin-api'; +import { NotFoundError } from '@backstage/errors'; +/** + * @public + */ export const vaultApiRef = createApiRef({ id: 'plugin.vault.service', }); +/** + * Object containing the secret name and some links. + * @public + */ export type VaultSecret = { name: string; showUrl: string; editUrl: string; }; +/** + * Interface for the VaultApi. + * @public + */ export interface VaultApi { + /** + * Returns a list of secrets used to show in a table. + * @param secretPath - The path where the secrets are stored in Vault + */ listSecrets(secretPath: string): Promise; } +/** + * Default implementation of the VaultApi. + * @public + */ export class VaultClient implements VaultApi { private readonly discoveryApi: DiscoveryApi; @@ -39,7 +59,7 @@ export class VaultClient implements VaultApi { private async callApi( path: string, query: { [key in string]: any }, - ): Promise { + ): Promise { const apiUrl = `${await this.discoveryApi.getBaseUrl('vault')}`; const response = await fetch( `${apiUrl}/${path}?${new URLSearchParams(query).toString()}`, @@ -49,23 +69,21 @@ export class VaultClient implements VaultApi { }, }, ); - if (response.status === 200) { + if (response.ok) { return (await response.json()) as T; + } else if (response.status === 404) { + throw new NotFoundError(`No secrets found in path '${path}'`); } - return undefined; + throw new Error( + `Unexpected error while fetching secrets from path '${path}'`, + ); } async listSecrets(secretPath: string): Promise { - if (secretPath === '') { - return []; - } - const result = await this.callApi( + const result = await this.callApi<{ items: VaultSecret[] }>( `v1/secrets/${encodeURIComponent(secretPath)}`, {}, ); - if (!result) { - return []; - } - return result; + return result.items; } } diff --git a/plugins/vault/src/components/EntityVaultTable/EntityVaultTable.test.tsx b/plugins/vault/src/components/EntityVaultTable/EntityVaultTable.test.tsx index f2b181562a..f3d7748b3e 100644 --- a/plugins/vault/src/components/EntityVaultTable/EntityVaultTable.test.tsx +++ b/plugins/vault/src/components/EntityVaultTable/EntityVaultTable.test.tsx @@ -68,18 +68,20 @@ describe('EntityVaultTable', () => { }, }; - const mockSecretsResult: VaultSecret[] = [ - { - name: 'secret::one', - editUrl: `${mockBaseUrl}/ui/vault/secrets/secrets/edit/test/success/secret::one`, - showUrl: `${mockBaseUrl}/ui/vault/secrets/secrets/show/test/success/secret::one`, - }, - { - name: 'secret::two', - editUrl: `${mockBaseUrl}/ui/vault/secrets/secrets/edit/test/success/secret::two`, - showUrl: `${mockBaseUrl}/ui/vault/secrets/secrets/show/test/success/secret::two`, - }, - ]; + const mockSecretsResult: { items: VaultSecret[] } = { + items: [ + { + name: 'secret::one', + editUrl: `${mockBaseUrl}/ui/vault/secrets/secrets/edit/test/success/secret::one`, + showUrl: `${mockBaseUrl}/ui/vault/secrets/secrets/show/test/success/secret::one`, + }, + { + name: 'secret::two', + editUrl: `${mockBaseUrl}/ui/vault/secrets/secrets/edit/test/success/secret::two`, + showUrl: `${mockBaseUrl}/ui/vault/secrets/secrets/show/test/success/secret::two`, + }, + ], + }; const setupHandlers = () => { server.use( diff --git a/plugins/vault/src/components/EntityVaultTable/EntityVaultTable.tsx b/plugins/vault/src/components/EntityVaultTable/EntityVaultTable.tsx index 197c82c5b8..8311dea2aa 100644 --- a/plugins/vault/src/components/EntityVaultTable/EntityVaultTable.tsx +++ b/plugins/vault/src/components/EntityVaultTable/EntityVaultTable.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; import { Entity } from '@backstage/catalog-model'; -import { Table, TableColumn } from '@backstage/core-components'; +import { Link, Table, TableColumn } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import { Box, Typography } from '@material-ui/core'; import Edit from '@material-ui/icons/Edit'; @@ -27,7 +27,7 @@ import { VAULT_SECRET_PATH_ANNOTATION } from '../../constants'; export const vaultSecretPath = (entity: Entity) => { const secretPath = - entity.metadata.annotations?.[VAULT_SECRET_PATH_ANNOTATION] ?? ''; + entity.metadata.annotations?.[VAULT_SECRET_PATH_ANNOTATION]; return { secretPath }; }; @@ -35,6 +35,11 @@ export const vaultSecretPath = (entity: Entity) => { export const EntityVaultTable = ({ entity }: { entity: Entity }) => { const vaultApi = useApi(vaultApiRef); const { secretPath } = vaultSecretPath(entity); + if (!secretPath) { + throw Error( + `The secret path is undefined. Please, define the annotation ${VAULT_SECRET_PATH_ANNOTATION}`, + ); + } const { value, loading, error } = useAsync(async (): Promise< VaultSecret[] > => { @@ -51,22 +56,22 @@ export const EntityVaultTable = ({ entity }: { entity: Entity }) => { return { secret: secret.name, view: ( - - + ), edit: ( - - + ), }; }); diff --git a/plugins/vault/src/conditions.ts b/plugins/vault/src/conditions.ts index 0ce34085e0..091c7117c2 100644 --- a/plugins/vault/src/conditions.ts +++ b/plugins/vault/src/conditions.ts @@ -16,6 +16,12 @@ import { Entity } from '@backstage/catalog-model'; import { VAULT_SECRET_PATH_ANNOTATION } from './constants'; +/** + * Checks if an entity contains the annotation for Vault. + * @param entity - The entity to check if the annotation exists + * @returns If the annotation exists or not + * @public + */ export function isVaultAvailable(entity: Entity): boolean { return Boolean( entity.metadata.annotations?.hasOwnProperty(VAULT_SECRET_PATH_ANNOTATION), diff --git a/plugins/vault/src/constants.ts b/plugins/vault/src/constants.ts index 1ac8b80b73..0d99c261d4 100644 --- a/plugins/vault/src/constants.ts +++ b/plugins/vault/src/constants.ts @@ -13,4 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +/** + * @public + */ export const VAULT_SECRET_PATH_ANNOTATION = 'vault.io/secrets-path'; diff --git a/plugins/vault/src/index.ts b/plugins/vault/src/index.ts index c2d1fc3419..f69a787963 100644 --- a/plugins/vault/src/index.ts +++ b/plugins/vault/src/index.ts @@ -14,3 +14,7 @@ * limitations under the License. */ export { vaultPlugin, EntityVaultCard } from './plugin'; +export { isVaultAvailable } from './conditions'; +export { VAULT_SECRET_PATH_ANNOTATION } from './constants'; +export { vaultApiRef } from './api'; +export type { VaultApi, VaultSecret } from './api'; diff --git a/yarn.lock b/yarn.lock index e30e3ec737..5454ec7513 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9718,7 +9718,7 @@ component-emitter@1.2.1: resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= -component-emitter@^1.2.0, component-emitter@^1.2.1, component-emitter@^1.3.0, component-emitter@~1.3.0: +component-emitter@^1.2.1, component-emitter@^1.3.0, component-emitter@~1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== @@ -10032,7 +10032,7 @@ cookie@0.5.0, cookie@~0.5.0: resolved "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== -cookiejar@^2.1.0, cookiejar@^2.1.3: +cookiejar@^2.1.3: version "2.1.3" resolved "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc" integrity sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ== @@ -13048,7 +13048,7 @@ form-data-encoder@^1.4.3, form-data-encoder@^1.7.1: resolved "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.1.tgz#ac80660e4f87ee0d3d3c3638b7da8278ddb8ec96" integrity sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg== -form-data@^2.3.1, form-data@^2.3.2, form-data@^2.5.0: +form-data@^2.3.2, form-data@^2.5.0: version "2.5.1" resolved "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== @@ -13105,11 +13105,6 @@ formdata-node@^4.3.1: node-domexception "1.0.0" web-streams-polyfill "4.0.0-beta.1" -formidable@^1.2.0: - version "1.2.6" - resolved "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz#d2a51d60162bbc9b4a055d8457a7c75315d1a168" - integrity sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ== - formidable@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz#4310bc7965d185536f9565184dee74fbb75557ff" @@ -17954,7 +17949,7 @@ meros@^1.1.4: resolved "https://registry.npmjs.org/meros/-/meros-1.1.4.tgz#c17994d3133db8b23807f62bec7f0cb276cfd948" integrity sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ== -methods@^1.0.0, methods@^1.1.1, methods@^1.1.2, methods@~1.1.2: +methods@^1.0.0, methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= @@ -18293,7 +18288,7 @@ mime-types@^2.0.8, mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, m dependencies: mime-db "1.52.0" -mime@1.6.0, mime@^1.3.4, mime@^1.4.1: +mime@1.6.0, mime@^1.3.4: version "1.6.0" resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== @@ -21148,7 +21143,7 @@ q@^1.5.1: resolved "https://registry.npmjs.org/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= -qs@6.10.3, qs@^6.10.1, qs@^6.10.2, qs@^6.10.3, qs@^6.5.1, qs@^6.9.1, qs@^6.9.4, qs@^6.9.6: +qs@6.10.3, qs@^6.10.1, qs@^6.10.2, qs@^6.10.3, qs@^6.9.1, qs@^6.9.4, qs@^6.9.6: version "6.10.3" resolved "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== @@ -23974,22 +23969,6 @@ sucrase@^3.18.0, sucrase@^3.20.2: pirates "^4.0.1" ts-interface-checker "^0.1.9" -superagent@^3.8.3: - version "3.8.3" - resolved "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128" - integrity sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA== - dependencies: - component-emitter "^1.2.0" - cookiejar "^2.1.0" - debug "^3.1.0" - extend "^3.0.0" - form-data "^2.3.1" - formidable "^1.2.0" - methods "^1.1.1" - mime "^1.4.1" - qs "^6.5.1" - readable-stream "^2.3.5" - superagent@^7.1.3: version "7.1.3" resolved "https://registry.npmjs.org/superagent/-/superagent-7.1.3.tgz#783ff8330e7c2dad6ad8f0095edc772999273b6b" @@ -24007,14 +23986,6 @@ superagent@^7.1.3: readable-stream "^3.6.0" semver "^7.3.7" -supertest@^4.0.2: - version "4.0.2" - resolved "https://registry.npmjs.org/supertest/-/supertest-4.0.2.tgz#c2234dbdd6dc79b6f15b99c8d6577b90e4ce3f36" - integrity sha512-1BAbvrOZsGA3YTCWqbmh14L0YEq0EGICX/nBnfkfVJn7SrxQV1I3pMYjSzG9y/7ZU2V9dWqyqk2POwxlb09duQ== - dependencies: - methods "^1.1.2" - superagent "^3.8.3" - supertest@^6.1.3, supertest@^6.1.6: version "6.2.3" resolved "https://registry.npmjs.org/supertest/-/supertest-6.2.3.tgz#291b220126e5faa654d12abe1ada3658757c8c67" From 5ebf2c70237a3b4c0ab4a4dd11967aad69505ef2 Mon Sep 17 00:00:00 2001 From: ivgo Date: Thu, 16 Jun 2022 12:42:56 +0200 Subject: [PATCH 2/3] Use p-limit to list secrets, update api-report & add changesets Signed-off-by: ivgo --- .changeset/rude-llamas-lie.md | 5 +++++ .changeset/sharp-planes-turn.md | 5 +++++ plugins/vault-backend/package.json | 1 + plugins/vault-backend/src/service/vaultApi.ts | 11 +++++++++-- plugins/vault/api-report.md | 2 -- 5 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 .changeset/rude-llamas-lie.md create mode 100644 .changeset/sharp-planes-turn.md diff --git a/.changeset/rude-llamas-lie.md b/.changeset/rude-llamas-lie.md new file mode 100644 index 0000000000..918d61da6d --- /dev/null +++ b/.changeset/rude-llamas-lie.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-vault': patch +--- + +Export missing parameters and added them to the api-report. Also adapted the API to the expected response from the backend diff --git a/.changeset/sharp-planes-turn.md b/.changeset/sharp-planes-turn.md new file mode 100644 index 0000000000..cc7045a9f7 --- /dev/null +++ b/.changeset/sharp-planes-turn.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-vault-backend': patch +--- + +Throw exceptions instead of swallow them, remove some exported types from the `api-report`, small changes in the API responses & expose the vault `baseUrl` to the frontend as well diff --git a/plugins/vault-backend/package.json b/plugins/vault-backend/package.json index 080398ea52..d0e3990aee 100644 --- a/plugins/vault-backend/package.json +++ b/plugins/vault-backend/package.json @@ -46,6 +46,7 @@ "express": "^4.17.1", "express-promise-router": "^4.1.0", "helmet": "^5.0.2", + "p-limit": "^3.1.0", "winston": "^3.7.2", "yn": "^5.0.0" }, diff --git a/plugins/vault-backend/src/service/vaultApi.ts b/plugins/vault-backend/src/service/vaultApi.ts index ad399dc6ac..14607a694f 100644 --- a/plugins/vault-backend/src/service/vaultApi.ts +++ b/plugins/vault-backend/src/service/vaultApi.ts @@ -17,6 +17,7 @@ import { Config } from '@backstage/config'; import { NotFoundError } from '@backstage/errors'; import fetch from 'cross-fetch'; +import plimit from 'p-limit'; import { getVaultConfig, VaultConfig } from '../config'; /** @@ -75,6 +76,7 @@ export interface VaultApi { */ export class VaultClient implements VaultApi { private vaultConfig: VaultConfig; + private readonly limit = plimit(5); constructor({ config }: { config: Config }) { this.vaultConfig = getVaultConfig(config); @@ -115,14 +117,19 @@ export class VaultClient implements VaultApi { this.vaultConfig.kvVersion === 2 ? `v1/${this.vaultConfig.secretEngine}/metadata/${secretPath}` : `v1/${this.vaultConfig.secretEngine}/${secretPath}`; - const result = await this.callApi(listUrl, { list: true }); + const result = await this.limit(() => + this.callApi(listUrl, { list: true }), + ); const secrets: VaultSecret[] = []; + await Promise.all( result.data.keys.map(async secret => { if (secret.endsWith('/')) { secrets.push( - ...(await this.listSecrets(`${secretPath}/${secret.slice(0, -1)}`)), + ...(await this.limit(() => + this.listSecrets(`${secretPath}/${secret.slice(0, -1)}`), + )), ); } else { secrets.push({ diff --git a/plugins/vault/api-report.md b/plugins/vault/api-report.md index 215ee274ec..f6fa8d1269 100644 --- a/plugins/vault/api-report.md +++ b/plugins/vault/api-report.md @@ -12,8 +12,6 @@ import { Entity } from '@backstage/catalog-model'; // @public export const EntityVaultCard: () => JSX.Element; -// Warning: (ae-missing-release-tag) "isVaultAvailable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export function isVaultAvailable(entity: Entity): boolean; From 886e1e8c810fad625c2e45823e26f914d3048180 Mon Sep 17 00:00:00 2001 From: ivgo Date: Mon, 20 Jun 2022 11:16:36 +0200 Subject: [PATCH 3/3] Set changeset to minor Signed-off-by: ivgo --- .changeset/sharp-planes-turn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/sharp-planes-turn.md b/.changeset/sharp-planes-turn.md index cc7045a9f7..47f6a6176d 100644 --- a/.changeset/sharp-planes-turn.md +++ b/.changeset/sharp-planes-turn.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-vault-backend': patch +'@backstage/plugin-vault-backend': minor --- Throw exceptions instead of swallow them, remove some exported types from the `api-report`, small changes in the API responses & expose the vault `baseUrl` to the frontend as well