From e8a1c1afe21b78a8b299e1135af99cc766248479 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Wed, 13 Oct 2021 16:24:07 +0200 Subject: [PATCH 01/65] Don't require a validation pattern for the jenkins base URL Signed-off-by: Dominik Henneke --- .changeset/flat-dodos-bake.md | 5 +++++ plugins/jenkins-backend/config.d.ts | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 .changeset/flat-dodos-bake.md diff --git a/.changeset/flat-dodos-bake.md b/.changeset/flat-dodos-bake.md new file mode 100644 index 0000000000..f6b52aac1a --- /dev/null +++ b/.changeset/flat-dodos-bake.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-jenkins-backend': patch +--- + +Don't require a validation pattern for the Jenkins base URL. diff --git a/plugins/jenkins-backend/config.d.ts b/plugins/jenkins-backend/config.d.ts index bfb519bb58..1fc41c1592 100644 --- a/plugins/jenkins-backend/config.d.ts +++ b/plugins/jenkins-backend/config.d.ts @@ -17,7 +17,6 @@ export interface Config { jenkins?: { /** * Default instance baseUrl, can be specified on a named instance called "default" - * @pattern "^https?://" */ baseUrl?: string; /** @@ -39,9 +38,6 @@ export interface Config { */ name: string; - /** - * @pattern "^https?://" - */ baseUrl: string; username: string; /** @visibility secret */ From 0611f3b3e2d5e4e742abbf17d6e17c1bd0c95541 Mon Sep 17 00:00:00 2001 From: Matto Date: Mon, 27 Sep 2021 23:41:24 +1000 Subject: [PATCH 02/65] Read config from remote config server Signed-off-by: Matto --- .changeset/giant-years-help.md | 8 + packages/backend-common/src/config.ts | 10 +- packages/cli/package.json | 3 +- packages/cli/src/lib/config.ts | 16 +- packages/config-loader/api-report.md | 34 +++- packages/config-loader/package.json | 10 +- packages/config-loader/src/index.ts | 2 +- packages/config-loader/src/loader.test.ts | 125 +++++++++++- packages/config-loader/src/loader.ts | 232 +++++++++++++++++++--- packages/integration/api-report.md | 5 + packages/integration/src/index.ts | 1 + yarn.lock | 69 ++++++- 12 files changed, 461 insertions(+), 54 deletions(-) create mode 100644 .changeset/giant-years-help.md diff --git a/.changeset/giant-years-help.md b/.changeset/giant-years-help.md new file mode 100644 index 0000000000..f0ca12ef19 --- /dev/null +++ b/.changeset/giant-years-help.md @@ -0,0 +1,8 @@ +--- +'@backstage/backend-common': patch +'@backstage/cli': patch +'@backstage/config-loader': patch +'@backstage/integration': patch +--- + +Reading app config from a remote server diff --git a/packages/backend-common/src/config.ts b/packages/backend-common/src/config.ts index 96907870cb..afdb4fd0f7 100644 --- a/packages/backend-common/src/config.ts +++ b/packages/backend-common/src/config.ts @@ -19,7 +19,8 @@ import parseArgs from 'minimist'; import { Logger } from 'winston'; import { findPaths } from '@backstage/cli-common'; import { Config, ConfigReader, JsonValue } from '@backstage/config'; -import { loadConfig } from '@backstage/config-loader'; +import { ConfigTarget, loadConfig } from '@backstage/config-loader'; +import { isValidUrl } from '@backstage/integration'; class ObservableConfigProxy implements Config { private config: Config = new ConfigReader({}); @@ -117,7 +118,10 @@ export async function loadBackendConfig(options: { argv: string[]; }): Promise { const args = parseArgs(options.argv); - const configPaths: string[] = [args.config ?? []].flat(); + + const configTargets: ConfigTarget[] = [args.config ?? []] + .flat() + .map(arg => (isValidUrl(arg) ? { url: arg } : { path: resolvePath(arg) })); const config = new ObservableConfigProxy(options.logger); @@ -126,7 +130,7 @@ export async function loadBackendConfig(options: { const configs = await loadConfig({ configRoot: paths.targetRoot, - configPaths: configPaths.map(opt => resolvePath(opt)), + configTargets: configTargets, watch: { onChange(newConfigs) { options.logger.info( diff --git a/packages/cli/package.json b/packages/cli/package.json index 0dc4dc3cf7..47475780a6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -31,8 +31,10 @@ "@babel/core": "^7.4.4", "@babel/plugin-transform-modules-commonjs": "^7.4.4", "@backstage/cli-common": "^0.1.3", + "@backstage/integration": "^0.6.5", "@backstage/config": "^0.1.10", "@backstage/config-loader": "^0.6.8", + "@backstage/backend-common": "^0.9.4", "@hot-loader/react-dom": "^16.13.0", "@lerna/package-graph": "^4.0.0", "@lerna/project": "^4.0.0", @@ -117,7 +119,6 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-common": "^0.9.4", "@backstage/config": "^0.1.10", "@backstage/core-components": "^0.5.0", "@backstage/core-plugin-api": "^0.1.8", diff --git a/packages/cli/src/lib/config.ts b/packages/cli/src/lib/config.ts index a93f040122..218771beb6 100644 --- a/packages/cli/src/lib/config.ts +++ b/packages/cli/src/lib/config.ts @@ -14,9 +14,14 @@ * limitations under the License. */ -import { loadConfig, loadConfigSchema } from '@backstage/config-loader'; +import { + ConfigTarget, + loadConfig, + loadConfigSchema, +} from '@backstage/config-loader'; import { ConfigReader } from '@backstage/config'; import { paths } from './paths'; +import { isValidUrl } from '@backstage/integration'; type Options = { args: string[]; @@ -26,7 +31,12 @@ type Options = { }; export async function loadCliConfig(options: Options) { - const configPaths = options.args.map(arg => paths.resolveTarget(arg)); + const configTargets: ConfigTarget[] = []; + options.args.forEach(arg => { + if (!isValidUrl(arg)) { + configTargets.push({ path: paths.resolveTarget(arg) }); + } + }); // Consider all packages in the monorepo when loading in config const { Project } = require('@lerna/project'); @@ -46,7 +56,7 @@ export async function loadCliConfig(options: Options) { ? async name => process.env[name] || 'x' : undefined, configRoot: paths.targetRoot, - configPaths, + configTargets: configTargets, }); // printing to stderr to not clobber stdout in case the cli command diff --git a/packages/config-loader/api-report.md b/packages/config-loader/api-report.md index bb3ae50d23..bb36bbe750 100644 --- a/packages/config-loader/api-report.md +++ b/packages/config-loader/api-report.md @@ -23,6 +23,17 @@ export type ConfigSchemaProcessingOptions = { withFilteredKeys?: boolean; }; +// Warning: (ae-missing-release-tag) "ConfigTarget" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ConfigTarget = + | { + path: string; + } + | { + url: string; + }; + // @public export type ConfigVisibility = 'frontend' | 'backend' | 'secret'; @@ -35,13 +46,11 @@ export function loadConfig(options: LoadConfigOptions): Promise; // @public (undocumented) export type LoadConfigOptions = { configRoot: string; - configPaths: string[]; + configTargets: ConfigTarget[]; env?: string; experimentalEnvFunc?: EnvFunc; - watch?: { - onChange: (configs: AppConfig[]) => void; - stopSignal?: Promise; - }; + remote?: Remote; + watch?: Watch; }; // @public @@ -66,6 +75,13 @@ export function readEnvConfig(env: { [name: string]: string | undefined; }): AppConfig[]; +// Warning: (ae-missing-release-tag) "Remote" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type Remote = { + reloadIntervalSeconds: number; +}; + // @public export type TransformFunc = ( value: T, @@ -73,4 +89,12 @@ export type TransformFunc = ( visibility: ConfigVisibility; }, ) => T | undefined; + +// Warning: (ae-missing-release-tag) "Watch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type Watch = { + onChange: (configs: AppConfig[]) => void; + stopSignal?: Promise; +}; ``` diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 2f2878799e..d20c77bedc 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -30,8 +30,10 @@ "clean": "backstage-cli clean" }, "dependencies": { + "@backstage/integration": "^0.6.5", "@backstage/cli-common": "^0.1.3", "@backstage/config": "^0.1.9", + "@backstage/backend-common": "^0.9.4", "@types/json-schema": "^7.0.6", "ajv": "^7.0.3", "chokidar": "^3.5.2", @@ -39,8 +41,10 @@ "json-schema": "^0.3.0", "json-schema-merge-allof": "^0.8.1", "typescript-json-schema": "^0.50.1", + "uuid": "^8.3.2", "yaml": "^1.9.2", - "yup": "^0.29.3" + "yup": "^0.29.3", + "node-fetch": "2.6.5" }, "devDependencies": { "@types/jest": "^26.0.7", @@ -48,7 +52,9 @@ "@types/mock-fs": "^4.10.0", "@types/node": "^14.14.32", "@types/yup": "^0.29.8", - "mock-fs": "^5.1.0" + "mock-fs": "^5.1.0", + "fetch-mock-jest": "1.5.1", + "fetch-mock": "^9.11.0" }, "files": [ "dist" diff --git a/packages/config-loader/src/index.ts b/packages/config-loader/src/index.ts index 0e66c59ec4..e0eafc00bc 100644 --- a/packages/config-loader/src/index.ts +++ b/packages/config-loader/src/index.ts @@ -30,4 +30,4 @@ export type { TransformFunc, } from './lib'; export { loadConfig } from './loader'; -export type { LoadConfigOptions } from './loader'; +export type { ConfigTarget, LoadConfigOptions, Watch, Remote } from './loader'; diff --git a/packages/config-loader/src/loader.test.ts b/packages/config-loader/src/loader.test.ts index 3f51e76936..190904a870 100644 --- a/packages/config-loader/src/loader.test.ts +++ b/packages/config-loader/src/loader.test.ts @@ -18,12 +18,33 @@ import { AppConfig } from '@backstage/config'; import { loadConfig } from './loader'; import mockFs from 'mock-fs'; import fs from 'fs-extra'; +import { v4 as uuidv4 } from 'uuid'; + +const fetchMock = require('fetch-mock').sandbox(); +const nodeFetch = require('node-fetch'); + +nodeFetch.default = fetchMock; describe('loadConfig', () => { beforeEach(() => { process.env.MY_SECRET = 'is-secret'; process.env.SUBSTITUTE_ME = 'substituted'; + fetchMock.mock( + { + url: 'https://some.domain.io/app-config.yaml', + method: 'GET', + }, + { + body: `app: + title: Remote Example App + sessionKey: 'abc123' + escaped: \$\${Escaped} + `, + headers: { ETag: uuidv4().toString() }, + }, + ); + mockFs({ '/root/app-config.yaml': ` app: @@ -66,6 +87,7 @@ describe('loadConfig', () => { }); afterEach(() => { + fetchMock.restore(); mockFs.restore(); }); @@ -73,7 +95,7 @@ describe('loadConfig', () => { await expect( loadConfig({ configRoot: '/root', - configPaths: [], + configTargets: [], env: 'production', }), ).resolves.toEqual([ @@ -90,11 +112,37 @@ describe('loadConfig', () => { ]); }); + it('load config from remote path', async () => { + const configUrl = 'https://some.domain.io/app-config.yaml'; + + await expect( + loadConfig({ + configRoot: '/root', + configTargets: [{ url: configUrl }], + env: 'production', + remote: { + reloadIntervalSeconds: 30, + }, + }), + ).resolves.toEqual([ + { + context: configUrl, + data: { + app: { + title: 'Remote Example App', + sessionKey: 'abc123', + escaped: '${Escaped}', + }, + }, + }, + ]); + }); + it('loads config with secrets', async () => { await expect( loadConfig({ configRoot: '/root', - configPaths: ['/root/app-config.yaml'], + configTargets: [{ path: '/root/app-config.yaml' }], env: 'production', }), ).resolves.toEqual([ @@ -115,9 +163,9 @@ describe('loadConfig', () => { await expect( loadConfig({ configRoot: '/root', - configPaths: [ - '/root/app-config.yaml', - '/root/app-config.development.yaml', + configTargets: [ + { path: '/root/app-config.yaml' }, + { path: '/root/app-config.development.yaml' }, ], env: 'development', }), @@ -155,7 +203,7 @@ describe('loadConfig', () => { await expect( loadConfig({ configRoot: '/root', - configPaths: ['/root/app-config.substitute.yaml'], + configTargets: [{ path: '/root/app-config.substitute.yaml' }], env: 'development', }), ).resolves.toEqual([ @@ -180,7 +228,7 @@ describe('loadConfig', () => { await expect( loadConfig({ configRoot: '/root', - configPaths: [], + configTargets: [], watch: { onChange: onChange.resolve, stopSignal: stopSignal.promise, @@ -218,12 +266,73 @@ describe('loadConfig', () => { stopSignal.resolve(); }); + it('watches remote config urls', async () => { + const onChange = defer(); + const stopSignal = defer(); + + const configUrl = 'https://some.domain.io/app-config.yaml'; + await expect( + loadConfig({ + configRoot: '/root', + configTargets: [{ url: configUrl }], + watch: { + onChange: onChange.resolve, + stopSignal: stopSignal.promise, + }, + remote: { + reloadIntervalSeconds: 1, + }, + }), + ).resolves.toEqual([ + { + context: configUrl, + data: { + app: { + title: 'Remote Example App', + sessionKey: 'abc123', + escaped: '${Escaped}', + }, + }, + }, + ]); + + fetchMock.mock( + { + url: 'https://some.domain.io/app-config.yaml', + }, + { + body: `app: + title: NEW ReMOTe ExaMPLe App + sessionKey: 'abc123' + escaped: \$\${Escaped} + `, + headers: { ETag: uuidv4().toString() }, + }, + { overwriteRoutes: true }, + ); + + await expect(onChange.promise).resolves.toEqual([ + { + context: configUrl, + data: { + app: { + title: 'NEW ReMOTe ExaMPLe App', + sessionKey: 'abc123', + escaped: '${Escaped}', + }, + }, + }, + ]); + + stopSignal.resolve(); + }); + it('stops watching config files', async () => { const stopSignal = defer(); await loadConfig({ configRoot: '/root', - configPaths: [], + configTargets: [], watch: { onChange: () => { expect('not').toBe('called'); diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index 315b44c4ee..fb8d0aab33 100644 --- a/packages/config-loader/src/loader.ts +++ b/packages/config-loader/src/loader.ts @@ -17,23 +17,67 @@ import fs from 'fs-extra'; import yaml from 'yaml'; import chokidar from 'chokidar'; -import { resolve as resolvePath, dirname, isAbsolute, basename } from 'path'; +import { basename, dirname, isAbsolute, resolve as resolvePath } from 'path'; import { AppConfig } from '@backstage/config'; import { applyConfigTransforms, - readEnvConfig, createIncludeTransform, createSubstitutionTransform, + EnvFunc, + readEnvConfig, } from './lib'; -import { EnvFunc } from './lib/transform/types'; +import fetch from 'node-fetch'; +import { isValidUrl } from '@backstage/integration'; +export type ConfigTarget = { path: string } | { url: string }; + +export type Watch = { + /** + * A listener that is called when a config file is changed. + */ + onChange: (configs: AppConfig[]) => void; + + /** + * An optional signal that stops the watcher once the promise resolves. + */ + stopSignal?: Promise; +}; + +export type Remote = { + /** + * An optional remote config reloading period, in seconds + */ + reloadIntervalSeconds: number; +}; + +export type RemoteConfigProp = { + /** + * URL of the remote config + */ + url: string; + + /** + * Contents of the remote config + */ + content: string | null; + + /** + * An optional new ETag header value. Used when checking for updated config. + */ + newETag?: string; + + /** + * An optional old ETag header value. Used when checking for updated config + */ + oldETag?: string; +}; /** @public */ export type LoadConfigOptions = { // The root directory of the config loading context. Used to find default configs. configRoot: string; - // Absolute paths to load config files from. Configs from earlier paths have lower priority. - configPaths: string[]; + // Paths to load config files from. Configs from earlier paths have lower priority. + configTargets: ConfigTarget[]; /** @deprecated This option has been removed */ env?: string; @@ -45,22 +89,19 @@ export type LoadConfigOptions = { */ experimentalEnvFunc?: EnvFunc; + /** + * An optional remote config + */ + remote?: Remote; + /** * An optional configuration that enables watching of config files. */ - watch?: { - /** - * A listener that is called when a config file is changed. - */ - onChange: (configs: AppConfig[]) => void; - - /** - * An optional signal that stops the watcher once the promise resolves. - */ - stopSignal?: Promise; - }; + watch?: Watch; }; +const HTTP_RESPONSE_HEADER_ETAG = 'ETag'; + /** * Load configuration data. * @@ -69,12 +110,30 @@ export type LoadConfigOptions = { export async function loadConfig( options: LoadConfigOptions, ): Promise { - const { configRoot, experimentalEnvFunc: envFunc, watch } = options; - const configPaths = options.configPaths.slice(); + const { configRoot, experimentalEnvFunc: envFunc, watch, remote } = options; + + const configPaths: string[] = options.configTargets + .slice() + .filter((e): e is { path: string } => e.hasOwnProperty('path')) + .map(configTarget => configTarget.path); + + let configUrls: string[] = options.configTargets + .slice() + .filter((e): e is { url: string } => e.hasOwnProperty('url')) + .map(configTarget => configTarget.url); + + const remoteConfigProps: RemoteConfigProp[] = []; + + if (remote === undefined && configUrls.length > 0) { + console.warn( + `Remote config detected, however, this feature is turned off. Remote config will be ignored.`, + ); + configUrls = []; + } // If no paths are provided, we default to reading // `app-config.yaml` and, if it exists, `app-config.local.yaml` - if (configPaths.length === 0) { + if (configPaths.length === 0 && configUrls.length === 0) { configPaths.push(resolvePath(configRoot, 'app-config.yaml')); const localConfig = resolvePath(configRoot, 'app-config.local.yaml'); @@ -99,6 +158,7 @@ export async function loadConfig( const input = yaml.parse(await readFile(configPath)); const substitutionTransform = createSubstitutionTransform(env); + const data = await applyConfigTransforms(dir, input, [ createIncludeTransform(env, readFile, substitutionTransform), substitutionTransform, @@ -110,7 +170,56 @@ export async function loadConfig( return configs; }; - let fileConfigs; + const loadRemoteConfigFiles = async () => { + const configs: AppConfig[] = []; + + const readConfigFromUrl = async (remoteConfigProp: RemoteConfigProp) => { + const response = await fetch(remoteConfigProp.url); + if (!response.ok) { + throw new Error( + `Could not read config file at ${remoteConfigProp.url}`, + ); + } + + remoteConfigProp.oldETag = remoteConfigProp.newETag ?? undefined; + remoteConfigProp.newETag = + response.headers.get(HTTP_RESPONSE_HEADER_ETAG) ?? undefined; + remoteConfigProp.content = await response.text(); + + return remoteConfigProp; + }; + + for (let i = 0; i < configUrls.length; i++) { + const remoteConfigProp = await readConfigFromUrl({ + url: configUrls[i], + content: null, + }); + + if (!isValidUrl(remoteConfigProp.url)) { + throw new Error( + `Config load path is not valid: '${remoteConfigProp.url}'`, + ); + } + + const dir = configRoot; + if (!remoteConfigProp.content) { + throw new Error(`Config is not valid`); + } + const input = yaml.parse(remoteConfigProp.content); + const substitutionTransform = createSubstitutionTransform(env); + const data = await applyConfigTransforms(dir, input, [ + substitutionTransform, + ]); + + configs.push({ data, context: remoteConfigProp.url }); + + remoteConfigProps.push(remoteConfigProp); + } + + return configs; + }; + + let fileConfigs: AppConfig[]; try { fileConfigs = await loadConfigFiles(); } catch (error) { @@ -119,15 +228,23 @@ export async function loadConfig( ); } + let remoteConfigs: AppConfig[] = []; + if (remote) { + try { + remoteConfigs = await loadRemoteConfigFiles(); + } catch (error) { + throw new Error(`Failed to read remote configuration file, ${error}`); + } + } + const envConfigs = await readEnvConfig(process.env); - // Set up config file watching if requested by the caller - if (watch) { - let currentSerializedConfig = JSON.stringify(fileConfigs); - + const watchConfigFile = (watchProp: Watch) => { const watcher = chokidar.watch(configPaths, { usePolling: process.env.NODE_ENV === 'test', }); + + let currentSerializedConfig = JSON.stringify(fileConfigs); watcher.on('change', async () => { try { const newConfigs = await loadConfigFiles(); @@ -138,18 +255,77 @@ export async function loadConfig( } currentSerializedConfig = newSerializedConfig; - watch.onChange([...newConfigs, ...envConfigs]); + watchProp.onChange([...remoteConfigs, ...newConfigs, ...envConfigs]); } catch (error) { console.error(`Failed to reload configuration files, ${error}`); } }); - if (watch.stopSignal) { - watch.stopSignal.then(() => { + if (watchProp.stopSignal) { + watchProp.stopSignal.then(() => { watcher.close(); }); } + }; + + const watchRemoteConfig = (watchProp: Watch, remoteProp: Remote) => { + const hasConfigChanged = async (remoteConfigProp: RemoteConfigProp) => { + const requestProps = { method: 'HEAD' }; + const { headers } = await fetch(remoteConfigProp.url, requestProps); + remoteConfigProp.oldETag = remoteConfigProp.newETag ?? undefined; + remoteConfigProp.newETag = + headers.get(HTTP_RESPONSE_HEADER_ETAG) ?? undefined; + + return ( + remoteConfigProp.oldETag !== undefined && + remoteConfigProp.newETag !== undefined && + remoteConfigProp.oldETag !== remoteConfigProp.newETag + ); + }; + + let handle: NodeJS.Timeout | undefined; + try { + handle = setInterval(async () => { + console.info(`Checking for config update`); + for (const remoteConfigProp of remoteConfigProps) { + if (await hasConfigChanged(remoteConfigProp)) { + console.info(`Remote config change, reloading config ...`); + const newRemoteConfigs = await loadRemoteConfigFiles(); + watchProp.onChange([ + ...newRemoteConfigs, + ...fileConfigs, + ...envConfigs, + ]); + console.info(`Remote config reloaded`); + break; + } + } + }, remoteProp.reloadIntervalSeconds * 1000); + } catch (error) { + console.error(`Failed to reload configuration files, ${error}`); + } + + if (watchProp.stopSignal) { + watchProp.stopSignal.then(() => { + if (handle !== undefined) { + console.info(`Stopping remote config watch`); + clearInterval(handle); + handle = undefined; + } + }); + } + }; + + // Set up config file watching if requested by the caller + if (watch) { + watchConfigFile(watch); } - return [...fileConfigs, ...envConfigs]; + if (watch && remote) { + watchRemoteConfig(watch, remote); + } + + return remote + ? [...remoteConfigs, ...fileConfigs, ...envConfigs] + : [...fileConfigs, ...envConfigs]; } diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index 01f84383fe..e07f61eea6 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -347,6 +347,11 @@ export type GoogleGcsIntegrationConfig = { privateKey?: string; }; +// Warning: (ae-missing-release-tag) "isValidUrl" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export function isValidUrl(url: string): boolean; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (ae-missing-release-tag) "readAwsS3IntegrationConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/packages/integration/src/index.ts b/packages/integration/src/index.ts index 3d5f336e8d..b8992f52d2 100644 --- a/packages/integration/src/index.ts +++ b/packages/integration/src/index.ts @@ -27,6 +27,7 @@ export * from './gitlab'; export * from './googleGcs'; export * from './awsS3'; export { defaultScmResolveUrl } from './helpers'; +export { isValidUrl } from './helpers'; export { ScmIntegrations } from './ScmIntegrations'; export type { ScmIntegration, ScmIntegrationsGroup } from './types'; export type { ScmIntegrationRegistry } from './registry'; diff --git a/yarn.lock b/yarn.lock index 7657498ff7..87991e1bb5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11336,6 +11336,11 @@ core-js@^2.5.0: resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== +core-js@^3.0.0: + version "3.18.3" + resolved "https://registry.npmjs.org/core-js/-/core-js-3.18.3.tgz#86a0bba2d8ec3df860fefcc07a8d119779f01509" + integrity sha512-tReEhtMReZaPFVw7dajMx0vlsz3oOb8ajgPoHVYGxr8ErnZ6PcYEvvmjGmXlfpnxpkYSdOQttjB+MvVbCGfvLw== + core-js@^3.0.4, core-js@^3.6.5, core-js@^3.8.2: version "3.15.0" resolved "https://registry.npmjs.org/core-js/-/core-js-3.15.0.tgz#db9554ebce0b6fd90dc9b1f2465c841d2d055044" @@ -14037,6 +14042,29 @@ fetch-blob@2.1.2: resolved "https://registry.npmjs.org/fetch-blob/-/fetch-blob-2.1.2.tgz#a7805db1361bd44c1ef62bb57fb5fe8ea173ef3c" integrity sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow== +fetch-mock-jest@1.5.1: + version "1.5.1" + resolved "https://registry.npmjs.org/fetch-mock-jest/-/fetch-mock-jest-1.5.1.tgz#0e13df990d286d9239e284f12b279ed509bf53cd" + integrity sha512-+utwzP8C+Pax1GSka3nFXILWMY3Er2L+s090FOgqVNrNCPp0fDqgXnAHAJf12PLHi0z4PhcTaZNTz8e7K3fjqQ== + dependencies: + fetch-mock "^9.11.0" + +fetch-mock@^9.11.0: + version "9.11.0" + resolved "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.11.0.tgz#371c6fb7d45584d2ae4a18ee6824e7ad4b637a3f" + integrity sha512-PG1XUv+x7iag5p/iNHD4/jdpxL9FtVSqRMUQhPab4hVDt80T1MH5ehzVrL2IdXO9Q2iBggArFvPqjUbHFuI58Q== + dependencies: + "@babel/core" "^7.0.0" + "@babel/runtime" "^7.0.0" + core-js "^3.0.0" + debug "^4.1.1" + glob-to-regexp "^0.4.0" + is-subset "^0.1.1" + lodash.isequal "^4.5.0" + path-to-regexp "^2.2.1" + querystring "^0.2.0" + whatwg-url "^6.5.0" + fetch-readablestream@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/fetch-readablestream/-/fetch-readablestream-0.2.0.tgz#eaa6d1a76b12de2d4731a343393c6ccdcfe2c795" @@ -14867,7 +14895,7 @@ glob-to-regexp@^0.3.0: resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= -glob-to-regexp@^0.4.1: +glob-to-regexp@^0.4.0, glob-to-regexp@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== @@ -16851,6 +16879,11 @@ is-subdir@^1.1.1: dependencies: better-path-resolve "1.0.0" +is-subset@^0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" + integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY= + is-svg@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" @@ -18719,7 +18752,7 @@ lodash.isempty@^4.4.0: resolved "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" integrity sha1-b4bL7di+TsmHvpqvM8loTbGzHn4= -lodash.isequal@^4.0.0: +lodash.isequal@^4.0.0, lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= @@ -20192,6 +20225,13 @@ node-fetch@2.6.1, node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1: resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-fetch@2.6.5: + version "2.6.5" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz#42735537d7f080a7e5f78b6c549b7146be1742fd" + integrity sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ== + dependencies: + whatwg-url "^5.0.0" + node-forge@^0.10.0: version "0.10.0" resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" @@ -21526,6 +21566,11 @@ path-to-regexp@^1.7.0: dependencies: isarray "0.0.1" +path-to-regexp@^2.2.1: + version "2.4.0" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704" + integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w== + path-type@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -26576,6 +26621,11 @@ tr46@^2.0.2: dependencies: punycode "^2.1.1" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + "traverse@>=0.3.0 <0.4": version "0.3.9" resolved "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" @@ -27702,6 +27752,11 @@ web-namespaces@^1.0.0: resolved "https://registry.npmjs.org/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw== +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" @@ -27954,7 +28009,15 @@ whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== -whatwg-url@^6.4.1: +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +whatwg-url@^6.4.1, whatwg-url@^6.5.0: version "6.5.0" resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ== From befe7d122f5095122740d0878058ad684219c182 Mon Sep 17 00:00:00 2001 From: Matto Date: Tue, 19 Oct 2021 23:18:19 +1100 Subject: [PATCH 03/65] Added documentation Signed-off-by: Matto --- docs/conf/writing.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/conf/writing.md b/docs/conf/writing.md index 4288d23b17..86b844484b 100644 --- a/docs/conf/writing.md +++ b/docs/conf/writing.md @@ -1,7 +1,6 @@ --- -id: writing -title: Writing Backstage Configuration Files -description: Documentation on Writing Backstage Configuration Files +id: writing title: Writing Backstage Configuration Files description: Documentation on Writing +Backstage Configuration Files --- ## File Format @@ -67,13 +66,15 @@ production build. ## Configuration Files -It is possible to have multiple configuration files, both to support different -environments, but also to define configuration that is local to specific -packages. The configuration files to load are selected using a `--config ` -flag, and it is possible to load any number of files. Paths are relative to the -working directory of the executed process, for example `package/backend`. This -means that to select a config file in the repo root when running the backend, -you would use `--config ../../my-config.yaml`. +It is possible to have multiple configuration files (bundled and/or remote), +both to support different environments, but also to define configuration that is +local to specific packages. The configuration files to load are selected using a +`--config ` flag, and it is possible to load any number of +files. Paths are relative to the working directory of the executed process, for +example `package/backend`. This means that to select a config file in the repo +root when running the backend, you would use `--config ../../my-config.yaml`, +and for config file on a config server you would use +`--config https://some.domain.io/app-config.yaml` If no `config` flags are specified, the default behavior is to load `app-config.yaml` and, if it exists, `app-config.local.yaml` from the repo root. @@ -85,7 +86,7 @@ are NOT loaded. To include them you need to explicitly include them with a flag, for example: ```shell -yarn start --config ../../app-config.yaml --config ../../app-config.staging.yaml +yarn start --config ../../app-config.yaml --config ../../app-config.staging.yaml --config https://some.domain.io/app-config.yaml ``` All loaded configuration files are merged together using the following rules: From 74ceaa64175d5eecd33577a735ce447081b7f0bc Mon Sep 17 00:00:00 2001 From: Matto Date: Thu, 21 Oct 2021 18:20:36 +1100 Subject: [PATCH 04/65] Remove the dependency on Etag headers for reloading config Signed-off-by: Matto --- docs/conf/writing.md | 5 +- packages/cli/package.json | 2 +- packages/config-loader/package.json | 4 +- packages/config-loader/src/loader.test.ts | 79 +++++++++-------- packages/config-loader/src/loader.ts | 102 ++++++---------------- yarn.lock | 7 -- 6 files changed, 73 insertions(+), 126 deletions(-) diff --git a/docs/conf/writing.md b/docs/conf/writing.md index 86b844484b..a0e29820ed 100644 --- a/docs/conf/writing.md +++ b/docs/conf/writing.md @@ -1,6 +1,7 @@ --- -id: writing title: Writing Backstage Configuration Files description: Documentation on Writing -Backstage Configuration Files +id: writing +title: Writing Backstage Configuration Files +description: Documentation on Writing Backstage Configuration Files --- ## File Format diff --git a/packages/cli/package.json b/packages/cli/package.json index 47475780a6..519b45a5ad 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -34,7 +34,6 @@ "@backstage/integration": "^0.6.5", "@backstage/config": "^0.1.10", "@backstage/config-loader": "^0.6.8", - "@backstage/backend-common": "^0.9.4", "@hot-loader/react-dom": "^16.13.0", "@lerna/package-graph": "^4.0.0", "@lerna/project": "^4.0.0", @@ -119,6 +118,7 @@ "yn": "^4.0.0" }, "devDependencies": { + "@backstage/backend-common": "^0.9.4", "@backstage/config": "^0.1.10", "@backstage/core-components": "^0.5.0", "@backstage/core-plugin-api": "^0.1.8", diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index d20c77bedc..80e4121971 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -41,7 +41,6 @@ "json-schema": "^0.3.0", "json-schema-merge-allof": "^0.8.1", "typescript-json-schema": "^0.50.1", - "uuid": "^8.3.2", "yaml": "^1.9.2", "yup": "^0.29.3", "node-fetch": "2.6.5" @@ -53,8 +52,7 @@ "@types/node": "^14.14.32", "@types/yup": "^0.29.8", "mock-fs": "^5.1.0", - "fetch-mock-jest": "1.5.1", - "fetch-mock": "^9.11.0" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/packages/config-loader/src/loader.test.ts b/packages/config-loader/src/loader.test.ts index 190904a870..e8b5a8828b 100644 --- a/packages/config-loader/src/loader.test.ts +++ b/packages/config-loader/src/loader.test.ts @@ -18,33 +18,47 @@ import { AppConfig } from '@backstage/config'; import { loadConfig } from './loader'; import mockFs from 'mock-fs'; import fs from 'fs-extra'; -import { v4 as uuidv4 } from 'uuid'; - -const fetchMock = require('fetch-mock').sandbox(); -const nodeFetch = require('node-fetch'); - -nodeFetch.default = fetchMock; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; describe('loadConfig', () => { + const server = setupServer(); + const initialLoaderHandler = rest.get( + `https://some.domain.io/app-config.yaml`, + (_req, res, ctx) => { + return res( + ctx.body( + `app: + title: Remote Example App + sessionKey: 'abc123' + escaped: \$\${Escaped} + `, + ), + ); + }, + ); + + const reloadHandler = rest.get( + `https://some.domain.io/app-config.yaml`, + (_req, res, ctx) => { + return res( + ctx.body( + `app: + title: NEW ReMOTe ExaMPLe App + sessionKey: 'abc123' + escaped: \$\${Escaped} + `, + ), + ); + }, + ); + + beforeAll(() => server.listen()); + beforeEach(() => { process.env.MY_SECRET = 'is-secret'; process.env.SUBSTITUTE_ME = 'substituted'; - fetchMock.mock( - { - url: 'https://some.domain.io/app-config.yaml', - method: 'GET', - }, - { - body: `app: - title: Remote Example App - sessionKey: 'abc123' - escaped: \$\${Escaped} - `, - headers: { ETag: uuidv4().toString() }, - }, - ); - mockFs({ '/root/app-config.yaml': ` app: @@ -87,10 +101,12 @@ describe('loadConfig', () => { }); afterEach(() => { - fetchMock.restore(); mockFs.restore(); + server.resetHandlers(); }); + afterAll(() => server.close()); + it('load config from default path', async () => { await expect( loadConfig({ @@ -113,6 +129,8 @@ describe('loadConfig', () => { }); it('load config from remote path', async () => { + server.use(initialLoaderHandler); + const configUrl = 'https://some.domain.io/app-config.yaml'; await expect( @@ -267,6 +285,8 @@ describe('loadConfig', () => { }); it('watches remote config urls', async () => { + server.use(initialLoaderHandler); + const onChange = defer(); const stopSignal = defer(); @@ -296,20 +316,7 @@ describe('loadConfig', () => { }, ]); - fetchMock.mock( - { - url: 'https://some.domain.io/app-config.yaml', - }, - { - body: `app: - title: NEW ReMOTe ExaMPLe App - sessionKey: 'abc123' - escaped: \$\${Escaped} - `, - headers: { ETag: uuidv4().toString() }, - }, - { overwriteRoutes: true }, - ); + server.use(reloadHandler); await expect(onChange.promise).resolves.toEqual([ { diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index fb8d0aab33..b853d30d8a 100644 --- a/packages/config-loader/src/loader.ts +++ b/packages/config-loader/src/loader.ts @@ -50,27 +50,6 @@ export type Remote = { reloadIntervalSeconds: number; }; -export type RemoteConfigProp = { - /** - * URL of the remote config - */ - url: string; - - /** - * Contents of the remote config - */ - content: string | null; - - /** - * An optional new ETag header value. Used when checking for updated config. - */ - newETag?: string; - - /** - * An optional old ETag header value. Used when checking for updated config - */ - oldETag?: string; -}; /** @public */ export type LoadConfigOptions = { // The root directory of the config loading context. Used to find default configs. @@ -100,8 +79,6 @@ export type LoadConfigOptions = { watch?: Watch; }; -const HTTP_RESPONSE_HEADER_ETAG = 'ETag'; - /** * Load configuration data. * @@ -117,18 +94,15 @@ export async function loadConfig( .filter((e): e is { path: string } => e.hasOwnProperty('path')) .map(configTarget => configTarget.path); - let configUrls: string[] = options.configTargets + const configUrls: string[] = options.configTargets .slice() .filter((e): e is { url: string } => e.hasOwnProperty('url')) .map(configTarget => configTarget.url); - const remoteConfigProps: RemoteConfigProp[] = []; - if (remote === undefined && configUrls.length > 0) { - console.warn( + throw new Error( `Remote config detected, however, this feature is turned off. Remote config will be ignored.`, ); - configUrls = []; } // If no paths are provided, we default to reading @@ -173,47 +147,32 @@ export async function loadConfig( const loadRemoteConfigFiles = async () => { const configs: AppConfig[] = []; - const readConfigFromUrl = async (remoteConfigProp: RemoteConfigProp) => { - const response = await fetch(remoteConfigProp.url); + const readConfigFromUrl = async (url: string) => { + const response = await fetch(url); if (!response.ok) { - throw new Error( - `Could not read config file at ${remoteConfigProp.url}`, - ); + throw new Error(`Could not read config file at ${url}`); } - remoteConfigProp.oldETag = remoteConfigProp.newETag ?? undefined; - remoteConfigProp.newETag = - response.headers.get(HTTP_RESPONSE_HEADER_ETAG) ?? undefined; - remoteConfigProp.content = await response.text(); - - return remoteConfigProp; + return await response.text(); }; for (let i = 0; i < configUrls.length; i++) { - const remoteConfigProp = await readConfigFromUrl({ - url: configUrls[i], - content: null, - }); - - if (!isValidUrl(remoteConfigProp.url)) { - throw new Error( - `Config load path is not valid: '${remoteConfigProp.url}'`, - ); + const configUrl = configUrls[i]; + if (!isValidUrl(configUrl)) { + throw new Error(`Config load path is not valid: '${configUrl}'`); } - const dir = configRoot; - if (!remoteConfigProp.content) { + const remoteConfigContent = await readConfigFromUrl(configUrl); + if (!remoteConfigContent) { throw new Error(`Config is not valid`); } - const input = yaml.parse(remoteConfigProp.content); + const configYaml = yaml.parse(remoteConfigContent); const substitutionTransform = createSubstitutionTransform(env); - const data = await applyConfigTransforms(dir, input, [ + const data = await applyConfigTransforms(configRoot, configYaml, [ substitutionTransform, ]); - configs.push({ data, context: remoteConfigProp.url }); - - remoteConfigProps.push(remoteConfigProp); + configs.push({ data, context: configUrl }); } return configs; @@ -269,17 +228,12 @@ export async function loadConfig( }; const watchRemoteConfig = (watchProp: Watch, remoteProp: Remote) => { - const hasConfigChanged = async (remoteConfigProp: RemoteConfigProp) => { - const requestProps = { method: 'HEAD' }; - const { headers } = await fetch(remoteConfigProp.url, requestProps); - remoteConfigProp.oldETag = remoteConfigProp.newETag ?? undefined; - remoteConfigProp.newETag = - headers.get(HTTP_RESPONSE_HEADER_ETAG) ?? undefined; - + const hasConfigChanged = async ( + oldRemoteConfigs: AppConfig[], + newRemoteConfigs: AppConfig[], + ) => { return ( - remoteConfigProp.oldETag !== undefined && - remoteConfigProp.newETag !== undefined && - remoteConfigProp.oldETag !== remoteConfigProp.newETag + JSON.stringify(oldRemoteConfigs) !== JSON.stringify(newRemoteConfigs) ); }; @@ -287,18 +241,12 @@ export async function loadConfig( try { handle = setInterval(async () => { console.info(`Checking for config update`); - for (const remoteConfigProp of remoteConfigProps) { - if (await hasConfigChanged(remoteConfigProp)) { - console.info(`Remote config change, reloading config ...`); - const newRemoteConfigs = await loadRemoteConfigFiles(); - watchProp.onChange([ - ...newRemoteConfigs, - ...fileConfigs, - ...envConfigs, - ]); - console.info(`Remote config reloaded`); - break; - } + const newRemoteConfigs = await loadRemoteConfigFiles(); + if (await hasConfigChanged(remoteConfigs, newRemoteConfigs)) { + remoteConfigs = newRemoteConfigs; + console.info(`Remote config change, reloading config ...`); + watchProp.onChange([...remoteConfigs, ...fileConfigs, ...envConfigs]); + console.info(`Remote config reloaded`); } }, remoteProp.reloadIntervalSeconds * 1000); } catch (error) { diff --git a/yarn.lock b/yarn.lock index 87991e1bb5..00c91f7783 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14042,13 +14042,6 @@ fetch-blob@2.1.2: resolved "https://registry.npmjs.org/fetch-blob/-/fetch-blob-2.1.2.tgz#a7805db1361bd44c1ef62bb57fb5fe8ea173ef3c" integrity sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow== -fetch-mock-jest@1.5.1: - version "1.5.1" - resolved "https://registry.npmjs.org/fetch-mock-jest/-/fetch-mock-jest-1.5.1.tgz#0e13df990d286d9239e284f12b279ed509bf53cd" - integrity sha512-+utwzP8C+Pax1GSka3nFXILWMY3Er2L+s090FOgqVNrNCPp0fDqgXnAHAJf12PLHi0z4PhcTaZNTz8e7K3fjqQ== - dependencies: - fetch-mock "^9.11.0" - fetch-mock@^9.11.0: version "9.11.0" resolved "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.11.0.tgz#371c6fb7d45584d2ae4a18ee6824e7ad4b637a3f" From 988c5b8421a82a733cebafc7afcaf01683149a5c Mon Sep 17 00:00:00 2001 From: Matto Date: Thu, 21 Oct 2021 18:26:08 +1100 Subject: [PATCH 05/65] Removed unused dependency Signed-off-by: Matto --- packages/config-loader/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 80e4121971..4b5afb3d29 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -33,7 +33,6 @@ "@backstage/integration": "^0.6.5", "@backstage/cli-common": "^0.1.3", "@backstage/config": "^0.1.9", - "@backstage/backend-common": "^0.9.4", "@types/json-schema": "^7.0.6", "ajv": "^7.0.3", "chokidar": "^3.5.2", From 855d460611216a54d045df5b269c65f8641e94f9 Mon Sep 17 00:00:00 2001 From: Matto Date: Tue, 26 Oct 2021 21:50:49 +1100 Subject: [PATCH 06/65] Change variable name Signed-off-by: Matto --- packages/config-loader/api-report.md | 12 ++++++------ packages/config-loader/src/index.ts | 7 ++++++- packages/config-loader/src/loader.ts | 15 +++++++++------ 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/packages/config-loader/api-report.md b/packages/config-loader/api-report.md index bb36bbe750..1dad108c38 100644 --- a/packages/config-loader/api-report.md +++ b/packages/config-loader/api-report.md @@ -49,8 +49,8 @@ export type LoadConfigOptions = { configTargets: ConfigTarget[]; env?: string; experimentalEnvFunc?: EnvFunc; - remote?: Remote; - watch?: Watch; + remote?: LoadConfigOptionsRemote; + watch?: LoadConfigOptionsWatch; }; // @public @@ -75,10 +75,10 @@ export function readEnvConfig(env: { [name: string]: string | undefined; }): AppConfig[]; -// Warning: (ae-missing-release-tag) "Remote" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "LoadConfigOptionsRemote" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type Remote = { +export type LoadConfigOptionsRemote = { reloadIntervalSeconds: number; }; @@ -90,10 +90,10 @@ export type TransformFunc = ( }, ) => T | undefined; -// Warning: (ae-missing-release-tag) "Watch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "LoadConfigOptionsWatch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type Watch = { +export type LoadConfigOptionsWatch = { onChange: (configs: AppConfig[]) => void; stopSignal?: Promise; }; diff --git a/packages/config-loader/src/index.ts b/packages/config-loader/src/index.ts index e0eafc00bc..c6b8ef8cef 100644 --- a/packages/config-loader/src/index.ts +++ b/packages/config-loader/src/index.ts @@ -30,4 +30,9 @@ export type { TransformFunc, } from './lib'; export { loadConfig } from './loader'; -export type { ConfigTarget, LoadConfigOptions, Watch, Remote } from './loader'; +export type { + ConfigTarget, + LoadConfigOptions, + LoadConfigOptionsWatch, + LoadConfigOptionsRemote, +} from './loader'; diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index b853d30d8a..2e7a0df801 100644 --- a/packages/config-loader/src/loader.ts +++ b/packages/config-loader/src/loader.ts @@ -31,7 +31,7 @@ import { isValidUrl } from '@backstage/integration'; export type ConfigTarget = { path: string } | { url: string }; -export type Watch = { +export type LoadConfigOptionsWatch = { /** * A listener that is called when a config file is changed. */ @@ -43,7 +43,7 @@ export type Watch = { stopSignal?: Promise; }; -export type Remote = { +export type LoadConfigOptionsRemote = { /** * An optional remote config reloading period, in seconds */ @@ -71,12 +71,12 @@ export type LoadConfigOptions = { /** * An optional remote config */ - remote?: Remote; + remote?: LoadConfigOptionsRemote; /** * An optional configuration that enables watching of config files. */ - watch?: Watch; + watch?: LoadConfigOptionsWatch; }; /** @@ -198,7 +198,7 @@ export async function loadConfig( const envConfigs = await readEnvConfig(process.env); - const watchConfigFile = (watchProp: Watch) => { + const watchConfigFile = (watchProp: LoadConfigOptionsWatch) => { const watcher = chokidar.watch(configPaths, { usePolling: process.env.NODE_ENV === 'test', }); @@ -227,7 +227,10 @@ export async function loadConfig( } }; - const watchRemoteConfig = (watchProp: Watch, remoteProp: Remote) => { + const watchRemoteConfig = ( + watchProp: LoadConfigOptionsWatch, + remoteProp: LoadConfigOptionsRemote, + ) => { const hasConfigChanged = async ( oldRemoteConfigs: AppConfig[], newRemoteConfigs: AppConfig[], From 03b47a476dadb895b0b9d36628ac7ec1759f86d1 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Tue, 26 Oct 2021 14:01:32 +0200 Subject: [PATCH 07/65] catalog-react: export loadIdentityOwnerRefs and loadCatalogOwnerRefs all the way Signed-off-by: Himanshu Mishra --- .changeset/smart-penguins-compete.md | 5 +++++ plugins/catalog-react/api-report.md | 16 ++++++++++++++++ plugins/catalog-react/src/hooks/index.ts | 2 +- 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 .changeset/smart-penguins-compete.md diff --git a/.changeset/smart-penguins-compete.md b/.changeset/smart-penguins-compete.md new file mode 100644 index 0000000000..d3518600a6 --- /dev/null +++ b/.changeset/smart-penguins-compete.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +export `loadIdentityOwnerRefs` and `loadCatalogOwnerRefs` all the way diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index 06dccf4b7f..07a924bfa3 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -15,6 +15,7 @@ import { Context } from 'react'; import { Entity } from '@backstage/catalog-model'; import { EntityName } from '@backstage/catalog-model'; import { IconButton } from '@material-ui/core'; +import { IdentityApi } from '@backstage/core-plugin-api'; import { LinkProps } from '@backstage/core-components'; import { Observable } from '@backstage/types'; import { PropsWithChildren } from 'react'; @@ -726,6 +727,21 @@ export function getEntitySourceLocation( // @public export function isOwnerOf(owner: Entity, owned: Entity): boolean; +// Warning: (ae-missing-release-tag) "loadCatalogOwnerRefs" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function loadCatalogOwnerRefs( + catalogApi: CatalogApi, + identityOwnerRefs: string[], +): Promise; + +// Warning: (ae-missing-release-tag) "loadIdentityOwnerRefs" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function loadIdentityOwnerRefs( + identityApi: IdentityApi, +): Promise; + // Warning: (ae-missing-release-tag) "MockEntityListContextProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/plugins/catalog-react/src/hooks/index.ts b/plugins/catalog-react/src/hooks/index.ts index 38fc58222d..17c15e4ebb 100644 --- a/plugins/catalog-react/src/hooks/index.ts +++ b/plugins/catalog-react/src/hooks/index.ts @@ -37,4 +37,4 @@ export { useOwnUser } from './useOwnUser'; export { useRelatedEntities } from './useRelatedEntities'; export { useStarredEntities } from './useStarredEntities'; export { useStarredEntity } from './useStarredEntity'; -export { useEntityOwnership } from './useEntityOwnership'; +export * from './useEntityOwnership'; From 48bd2e2be206e76db903f7ded79dbcfca31d80b5 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Fri, 22 Oct 2021 14:57:02 +0100 Subject: [PATCH 08/65] chore(Snyk): Added workflow for outputting the Snyk report as JSON Signed-off-by: Harry Hogg Co-Authored-By: Himanshu Mishra Date: Mon, 25 Oct 2021 13:04:56 +0100 Subject: [PATCH 09/65] chore(Snyk): Added scripts for syncing Snyk vulnerabilities to Github issues. Signed-off-by: Harry Hogg --- package.json | 3 +- scripts/snyk-github-issue-sync.ts | 114 ++++++++++++++++++++++++++++++ yarn.lock | 78 ++++++++++++++++++-- 3 files changed, 189 insertions(+), 6 deletions(-) create mode 100644 scripts/snyk-github-issue-sync.ts diff --git a/package.json b/package.json index 4aa6905085..4f25429569 100644 --- a/package.json +++ b/package.json @@ -53,15 +53,16 @@ }, "version": "1.0.0", "dependencies": { + "@octokit/rest": "^18.12.0", "@microsoft/api-documenter": "^7.13.47", "@microsoft/api-extractor": "^7.18.7", "@microsoft/api-extractor-model": "^7.13.5", "@microsoft/tsdoc": "^0.13.2" }, "devDependencies": { - "@types/webpack": "^5.28.0", "@changesets/cli": "^2.14.0", "@spotify/prettier-config": "^11.0.0", + "@types/webpack": "^5.28.0", "command-exists": "^1.2.9", "concurrently": "^6.0.0", "eslint-plugin-notice": "^0.9.10", diff --git a/scripts/snyk-github-issue-sync.ts b/scripts/snyk-github-issue-sync.ts new file mode 100644 index 0000000000..e20d7d4164 --- /dev/null +++ b/scripts/snyk-github-issue-sync.ts @@ -0,0 +1,114 @@ +/* + * Copyright 2021 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 { Octokit } from '@octokit/rest'; +import syncJsonOutput from '../snyk.json'; + +type Vulnerability = { + description: string; + id: string; + packages: Set; +}; + +const octokit = new Octokit({ + auth: process.env.GITHUB_TOKEN, +}); + +const fetchSnykGithubIssueMap = async (): Promise> => { + const snykGithubIssueMap: Record = {}; + + const iterator = octokit.paginate.iterator(octokit.rest.issues.listForRepo, { + owner: 'backstage', + repo: 'backstage', + per_page: 100, + labels: 'snyk', + }); + + for await (const { data: issues } of iterator) { + for (const issue of issues) { + const match = /\([([A-Z0-9-]+)\])/.exec(issue.title); + + if (match && match[1]) { + snykGithubIssueMap[match[1]] = issue.id; + } + } + } + + return snykGithubIssueMap; +}; + +const createGithubIssue = (vulnerability: Vulnerability) => { + console.log( + `Create issue for vulnerability ${ + vulnerability.id + } affecting packages ${Array.from(vulnerability.packages)}`, + ); + // TODO(hhogg): Create github issue with the contents from a Snyk issue. +}; + +const updateGithubIssue = ( + githubIssueId: number, + vulnerability: Vulnerability, +) => { + console.log( + `Update issue ${githubIssueId} for vulnerability ${vulnerability.id}`, + ); + // TODO(hhogg): Update github issue with the contents from a Snyk issue. +}; + +const closeGithubIssue = (githubIssueId: number) => { + console.log(`Delete issue ${githubIssueId}`); + // TODO(hhogg): Delete a github issue +}; + +(async () => { + const snykGithubIssueMap = await fetchSnykGithubIssueMap(); + const vulnerabilityStore: Record = {}; + + // Group the Snyk vulnerabilities, and aggregate the affecting packages. + syncJsonOutput.forEach(({ projectName, vulnerabilities }) => { + vulnerabilities.forEach( + ({ id, description }: { id: string; description: string }) => { + if (id !== undefined && description !== undefined) { + if (vulnerabilityStore[id]) { + vulnerabilityStore[id].packages.add(projectName); + } else { + vulnerabilityStore[id] = { + description, + id, + packages: new Set([projectName]), + }; + } + } + }, + ); + }); + + // Loop over the grouped vulnerabilities and create/update accordingly + Object.entries(vulnerabilityStore).forEach(([id, vulnerability]) => { + if (snykGithubIssueMap[id]) { + updateGithubIssue(snykGithubIssueMap[id], vulnerability); + } else { + createGithubIssue(vulnerability); + } + }); + + // Loop over the Github issues and delete accordingly. + Object.entries(snykGithubIssueMap).forEach(([snykId, githubIssueId]) => { + if (!snykGithubIssueMap[snykId]) { + closeGithubIssue(githubIssueId); + } + }); +})(); diff --git a/yarn.lock b/yarn.lock index c905fbea6f..92e2d56f9d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4961,6 +4961,19 @@ before-after-hook "^2.1.0" universal-user-agent "^6.0.0" +"@octokit/core@^3.5.1": + version "3.5.1" + resolved "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz#8601ceeb1ec0e1b1b8217b960a413ed8e947809b" + integrity sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw== + dependencies: + "@octokit/auth-token" "^2.4.4" + "@octokit/graphql" "^4.5.8" + "@octokit/request" "^5.6.0" + "@octokit/request-error" "^2.0.5" + "@octokit/types" "^6.0.3" + before-after-hook "^2.2.0" + universal-user-agent "^6.0.0" + "@octokit/endpoint@^6.0.1": version "6.0.3" resolved "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.3.tgz#dd09b599662d7e1b66374a177ab620d8cdf73487" @@ -4995,6 +5008,11 @@ "@octokit/types" "^6.12.2" btoa-lite "^1.0.0" +"@octokit/openapi-types@^11.2.0": + version "11.2.0" + resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz#b38d7fc3736d52a1e96b230c1ccd4a58a2f400a6" + integrity sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA== + "@octokit/openapi-types@^7.3.2": version "7.3.2" resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-7.3.2.tgz#065ce49b338043ec7f741316ce06afd4d459d944" @@ -5005,6 +5023,13 @@ resolved "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz#e07896739618dab8da7d4077c658003775f95437" integrity sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw== +"@octokit/plugin-paginate-rest@^2.16.8": + version "2.17.0" + resolved "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz#32e9c7cab2a374421d3d0de239102287d791bce7" + integrity sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw== + dependencies: + "@octokit/types" "^6.34.0" + "@octokit/plugin-paginate-rest@^2.6.2": version "2.7.0" resolved "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.7.0.tgz#6bb7b043c246e0654119a6ec4e72a172c9e2c7f3" @@ -5017,6 +5042,11 @@ resolved "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.2.tgz#394d59ec734cd2f122431fbaf05099861ece3c44" integrity sha512-oTJSNAmBqyDR41uSMunLQKMX0jmEXbwD1fpz8FG27lScV3RhtGfBa1/BBLym+PxcC16IBlF7KH9vP1BUYxA+Eg== +"@octokit/plugin-request-log@^1.0.4": + version "1.0.4" + resolved "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" + integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== + "@octokit/plugin-rest-endpoint-methods@5.3.1": version "5.3.1" resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.3.1.tgz#deddce769b4ec3179170709ab42e4e9e6195aaa9" @@ -5025,6 +5055,14 @@ "@octokit/types" "^6.16.2" deprecation "^2.3.1" +"@octokit/plugin-rest-endpoint-methods@^5.12.0": + version "5.13.0" + resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz#8c46109021a3412233f6f50d28786f8e552427ba" + integrity sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA== + dependencies: + "@octokit/types" "^6.34.0" + deprecation "^2.3.1" + "@octokit/request-error@^2.0.0", "@octokit/request-error@^2.0.2", "@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": version "2.1.0" resolved "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677" @@ -5056,6 +5094,16 @@ "@octokit/plugin-request-log" "^1.0.2" "@octokit/plugin-rest-endpoint-methods" "5.3.1" +"@octokit/rest@^18.12.0": + version "18.12.0" + resolved "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz#f06bc4952fc87130308d810ca9d00e79f6988881" + integrity sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q== + dependencies: + "@octokit/core" "^3.5.1" + "@octokit/plugin-paginate-rest" "^2.16.8" + "@octokit/plugin-request-log" "^1.0.4" + "@octokit/plugin-rest-endpoint-methods" "^5.12.0" + "@octokit/types@^5.0.0", "@octokit/types@^5.0.1": version "5.5.0" resolved "https://registry.npmjs.org/@octokit/types/-/types-5.5.0.tgz#e5f06e8db21246ca102aa28444cdb13ae17a139b" @@ -5070,6 +5118,13 @@ dependencies: "@octokit/openapi-types" "^7.3.2" +"@octokit/types@^6.34.0": + version "6.34.0" + resolved "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz#c6021333334d1ecfb5d370a8798162ddf1ae8218" + integrity sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw== + dependencies: + "@octokit/openapi-types" "^11.2.0" + "@octokit/webhooks-methods@^2.0.0": version "2.0.0" resolved "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-2.0.0.tgz#1108b9ea661ca6c81e4a8bfa63a09eb27d5bc2db" @@ -7604,10 +7659,19 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@>=16.9.0", "@types/react@^16.9": - version "16.14.18" - resolved "https://registry.npmjs.org/@types/react/-/react-16.14.18.tgz#b2bcea05ee244fde92d409f91bd888ca8e54b20f" - integrity sha512-eeyqd1mqoG43mI0TvNKy9QNf1Tjz3DEOsRP3rlPo35OeMIt05I+v9RR8ZvL2GuYZeF2WAcLXJZMzu6zdz3VbtQ== +"@types/react@*", "@types/react@>=16.9.0": + version "17.0.33" + resolved "https://registry.npmjs.org/@types/react/-/react-17.0.33.tgz#e01ae3de7613dac1094569880bb3792732203ad5" + integrity sha512-pLWntxXpDPaU+RTAuSGWGSEL2FRTNyRQOjSWDke/rxRg14ncsZvx8AKWMWZqvc1UOaJIAoObdZhAWvRaHFi5rw== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/react@^16.9": + version "16.14.20" + resolved "https://registry.npmjs.org/@types/react/-/react-16.14.20.tgz#ff6e932ad71d92c27590e4a8667c7a53a7d0baad" + integrity sha512-SV7TaVc8e9E/5Xuv6TIyJ5VhQpZoVFJqX6IZgj5HZoFCtIDCArE3qXkcHlc6O/Ud4UwcMoX+tlvDA95YrKdLgA== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -9778,6 +9842,11 @@ before-after-hook@^2.1.0: resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635" integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A== +before-after-hook@^2.2.0: + version "2.2.2" + resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" + integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== + better-opn@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/better-opn/-/better-opn-2.1.1.tgz#94a55b4695dc79288f31d7d0e5f658320759f7c6" @@ -20351,7 +20420,6 @@ minipass-fetch@^1.3.0, minipass-fetch@^1.3.2: resolved "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.3.3.tgz#34c7cea038c817a8658461bf35174551dce17a0a" integrity sha512-akCrLDWfbdAWkMLBxJEeWTdNsjML+dt5YgOI4gJ53vuO0vrmYQkUPxa6j6V65s9CcePIr2SSWqjT2EcrNseryQ== dependencies: - encoding "^0.1.12" minipass "^3.1.0" minipass-sized "^1.0.3" minizlib "^2.0.0" From 3cb1e8e6ee5f7561905c639b98427fc9bd109e71 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Tue, 26 Oct 2021 14:16:29 +0200 Subject: [PATCH 10/65] fix types/react version Co-authored-by: Harry Hogg Signed-off-by: Himanshu Mishra --- package.json | 2 +- scripts/snyk-github-issue-sync.ts | 2 ++ yarn.lock | 17 ++++------------- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 4f25429569..77ec43ee24 100644 --- a/package.json +++ b/package.json @@ -53,13 +53,13 @@ }, "version": "1.0.0", "dependencies": { - "@octokit/rest": "^18.12.0", "@microsoft/api-documenter": "^7.13.47", "@microsoft/api-extractor": "^7.18.7", "@microsoft/api-extractor-model": "^7.13.5", "@microsoft/tsdoc": "^0.13.2" }, "devDependencies": { + "@octokit/rest": "^18.12.0", "@changesets/cli": "^2.14.0", "@spotify/prettier-config": "^11.0.0", "@types/webpack": "^5.28.0", diff --git a/scripts/snyk-github-issue-sync.ts b/scripts/snyk-github-issue-sync.ts index e20d7d4164..52e39da0cd 100644 --- a/scripts/snyk-github-issue-sync.ts +++ b/scripts/snyk-github-issue-sync.ts @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +// eslint-disable-next-line import/no-extraneous-dependencies import { Octokit } from '@octokit/rest'; +// The GitHub workflow .github/workflows/ import syncJsonOutput from '../snyk.json'; type Vulnerability = { diff --git a/yarn.lock b/yarn.lock index 92e2d56f9d..71b2fb89d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7659,19 +7659,10 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@>=16.9.0": - version "17.0.33" - resolved "https://registry.npmjs.org/@types/react/-/react-17.0.33.tgz#e01ae3de7613dac1094569880bb3792732203ad5" - integrity sha512-pLWntxXpDPaU+RTAuSGWGSEL2FRTNyRQOjSWDke/rxRg14ncsZvx8AKWMWZqvc1UOaJIAoObdZhAWvRaHFi5rw== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - -"@types/react@^16.9": - version "16.14.20" - resolved "https://registry.npmjs.org/@types/react/-/react-16.14.20.tgz#ff6e932ad71d92c27590e4a8667c7a53a7d0baad" - integrity sha512-SV7TaVc8e9E/5Xuv6TIyJ5VhQpZoVFJqX6IZgj5HZoFCtIDCArE3qXkcHlc6O/Ud4UwcMoX+tlvDA95YrKdLgA== +"@types/react@*", "@types/react@>=16.9.0", "@types/react@^16.9": + version "16.14.18" + resolved "https://registry.npmjs.org/@types/react/-/react-16.14.18.tgz#b2bcea05ee244fde92d409f91bd888ca8e54b20f" + integrity sha512-eeyqd1mqoG43mI0TvNKy9QNf1Tjz3DEOsRP3rlPo35OeMIt05I+v9RR8ZvL2GuYZeF2WAcLXJZMzu6zdz3VbtQ== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" From bf76bb7a1d88762307353dc623de86ab84eb1a34 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Tue, 26 Oct 2021 14:43:30 +0200 Subject: [PATCH 11/65] create github issue with a formatted body Co-authored-by: Harry Hogg Signed-off-by: Himanshu Mishra --- scripts/snyk-github-issue-sync.ts | 58 ++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/scripts/snyk-github-issue-sync.ts b/scripts/snyk-github-issue-sync.ts index 52e39da0cd..ed5082717d 100644 --- a/scripts/snyk-github-issue-sync.ts +++ b/scripts/snyk-github-issue-sync.ts @@ -15,15 +15,22 @@ */ // eslint-disable-next-line import/no-extraneous-dependencies import { Octokit } from '@octokit/rest'; -// The GitHub workflow .github/workflows/ -import syncJsonOutput from '../snyk.json'; +// Generated by GitHub workflow .github/workflows/snyk-github-issue-creator +import synkJsonOutput from '../snyk.json'; + +// Pattern for a GitHub Issue title +// Snyk vulnerability [Vulnerability ID] type Vulnerability = { description: string; - id: string; + snykId: string; packages: Set; }; +// Remember to fix me! +const GH_OWNER = 'orkohunter'; +const GH_REPO = 'backstage'; + const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN, }); @@ -32,14 +39,16 @@ const fetchSnykGithubIssueMap = async (): Promise> => { const snykGithubIssueMap: Record = {}; const iterator = octokit.paginate.iterator(octokit.rest.issues.listForRepo, { - owner: 'backstage', - repo: 'backstage', + // TODO(Harry/Himanshu): Use a CLI flag for these values. + owner: GH_OWNER, + repo: GH_REPO, per_page: 100, - labels: 'snyk', + labels: 'snyk-vulnerability', }); for await (const { data: issues } of iterator) { for (const issue of issues) { + // Gets the Vulnerability ID from square braces const match = /\([([A-Z0-9-]+)\])/.exec(issue.title); if (match && match[1]) { @@ -51,13 +60,31 @@ const fetchSnykGithubIssueMap = async (): Promise> => { return snykGithubIssueMap; }; +const generateIssueBody = (vulnerability: Vulnerability) => { + let issueBody = ''; + issueBody += '## Affecting Packages/Plugins\n'; + vulnerability.packages.forEach(pkgName => { + issueBody += `* ${pkgName}\n`; + }); + // TODO: Use displayTargetFile in snyk.json to create hyperlinks + issueBody += '\n'; + issueBody += vulnerability.description; + return issueBody; +}; + const createGithubIssue = (vulnerability: Vulnerability) => { console.log( `Create issue for vulnerability ${ - vulnerability.id + vulnerability.snykId } affecting packages ${Array.from(vulnerability.packages)}`, ); - // TODO(hhogg): Create github issue with the contents from a Snyk issue. + octokit.issues.create({ + owner: GH_OWNER, + repo: GH_REPO, + title: `Snyk vulnerability [${vulnerability.snykId}]`, + labels: ['snyk-vulnerability', 'help wanted'], + body: generateIssueBody(vulnerability), + }); }; const updateGithubIssue = ( @@ -65,7 +92,7 @@ const updateGithubIssue = ( vulnerability: Vulnerability, ) => { console.log( - `Update issue ${githubIssueId} for vulnerability ${vulnerability.id}`, + `Update issue ${githubIssueId} for vulnerability ${vulnerability.snykId}`, ); // TODO(hhogg): Update github issue with the contents from a Snyk issue. }; @@ -75,12 +102,12 @@ const closeGithubIssue = (githubIssueId: number) => { // TODO(hhogg): Delete a github issue }; -(async () => { +async function main() { const snykGithubIssueMap = await fetchSnykGithubIssueMap(); const vulnerabilityStore: Record = {}; // Group the Snyk vulnerabilities, and aggregate the affecting packages. - syncJsonOutput.forEach(({ projectName, vulnerabilities }) => { + synkJsonOutput.forEach(({ projectName, vulnerabilities }) => { vulnerabilities.forEach( ({ id, description }: { id: string; description: string }) => { if (id !== undefined && description !== undefined) { @@ -89,7 +116,7 @@ const closeGithubIssue = (githubIssueId: number) => { } else { vulnerabilityStore[id] = { description, - id, + snykId: id, packages: new Set([projectName]), }; } @@ -113,4 +140,9 @@ const closeGithubIssue = (githubIssueId: number) => { closeGithubIssue(githubIssueId); } }); -})(); +} + +main().catch(error => { + console.error(error.stack); + process.exit(1); +}); From 33d7bd0baaac3fa2d3b0b34d27b270f109953578 Mon Sep 17 00:00:00 2001 From: rodion Date: Tue, 26 Oct 2021 21:57:04 +0300 Subject: [PATCH 12/65] fix: sentry plugin can pass id token Signed-off-by: rodion --- .changeset/nasty-actors-push.md | 5 +++++ plugins/sentry/api-report.md | 18 +++++++++++++++++- plugins/sentry/src/api/production-api.ts | 21 ++++++++++++++++++++- plugins/sentry/src/plugin.ts | 10 ++++++++-- 4 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 .changeset/nasty-actors-push.md diff --git a/.changeset/nasty-actors-push.md b/.changeset/nasty-actors-push.md new file mode 100644 index 0000000000..8f4d0d92a8 --- /dev/null +++ b/.changeset/nasty-actors-push.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-sentry': patch +--- + +fix: sentry-plugin can forward identity token to backend (for case when it requires authorization) diff --git a/plugins/sentry/api-report.md b/plugins/sentry/api-report.md index a23fa112c1..1b7a248e48 100644 --- a/plugins/sentry/api-report.md +++ b/plugins/sentry/api-report.md @@ -9,6 +9,7 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; +import { IdentityApi } from '@backstage/core-plugin-api'; import { InfoCardVariants } from '@backstage/core-components'; import { RouteRef } from '@backstage/core-plugin-api'; @@ -34,7 +35,22 @@ export class MockSentryApi implements SentryApi { // // @public (undocumented) export class ProductionSentryApi implements SentryApi { - constructor(discoveryApi: DiscoveryApi, organization: string); + constructor( + discoveryApi: DiscoveryApi, + organization: string, + identityApi?: IdentityApi | undefined, + ); + // (undocumented) + authOptions(): Promise< + | { + headers?: undefined; + } + | { + headers: { + authorization: string; + }; + } + >; // (undocumented) fetchIssues( project: string, diff --git a/plugins/sentry/src/api/production-api.ts b/plugins/sentry/src/api/production-api.ts index 5968d20011..0632da711e 100644 --- a/plugins/sentry/src/api/production-api.ts +++ b/plugins/sentry/src/api/production-api.ts @@ -16,12 +16,13 @@ import { SentryIssue } from './sentry-issue'; import { SentryApi } from './sentry-api'; -import { DiscoveryApi } from '@backstage/core-plugin-api'; +import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; export class ProductionSentryApi implements SentryApi { constructor( private readonly discoveryApi: DiscoveryApi, private readonly organization: string, + private readonly identityApi?: IdentityApi, ) {} async fetchIssues( @@ -34,11 +35,13 @@ export class ProductionSentryApi implements SentryApi { } const apiUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/sentry/api`; + const options = await this.authOptions(); const queryPart = query ? `&query=${query}` : ''; const response = await fetch( `${apiUrl}/0/projects/${this.organization}/${project}/issues/?statsPeriod=${statsFor}${queryPart}`, + options, ); if (response.status >= 400 && response.status < 600) { @@ -47,4 +50,20 @@ export class ProductionSentryApi implements SentryApi { return (await response.json()) as SentryIssue[]; } + + async authOptions() { + if (!this.identityApi) { + return {}; + } + try { + const token = await this.identityApi.getIdToken(); + return { + headers: { + authorization: `Bearer ${token}`, + }, + }; + } catch (e) { + return {}; + } + } } diff --git a/plugins/sentry/src/plugin.ts b/plugins/sentry/src/plugin.ts index 75de6ba6dc..454c1f02a6 100644 --- a/plugins/sentry/src/plugin.ts +++ b/plugins/sentry/src/plugin.ts @@ -21,6 +21,7 @@ import { createPlugin, createRouteRef, discoveryApiRef, + identityApiRef, } from '@backstage/core-plugin-api'; export const rootRouteRef = createRouteRef({ @@ -33,11 +34,16 @@ export const sentryPlugin = createPlugin({ apis: [ createApiFactory({ api: sentryApiRef, - deps: { configApi: configApiRef, discoveryApi: discoveryApiRef }, - factory: ({ configApi, discoveryApi }) => + deps: { + configApi: configApiRef, + discoveryApi: discoveryApiRef, + identityApi: identityApiRef, + }, + factory: ({ configApi, discoveryApi, identityApi }) => new ProductionSentryApi( discoveryApi, configApi.getString('sentry.organization'), + identityApi, ), }), ], From 197601f21c2f307132f4f0038821e50301ce10de Mon Sep 17 00:00:00 2001 From: rodion Date: Wed, 27 Oct 2021 11:28:58 +0300 Subject: [PATCH 13/65] fix: sentry-plugin passing token - no need for catch Signed-off-by: rodion --- plugins/sentry/src/api/production-api.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/plugins/sentry/src/api/production-api.ts b/plugins/sentry/src/api/production-api.ts index 0632da711e..37a735df14 100644 --- a/plugins/sentry/src/api/production-api.ts +++ b/plugins/sentry/src/api/production-api.ts @@ -55,15 +55,11 @@ export class ProductionSentryApi implements SentryApi { if (!this.identityApi) { return {}; } - try { - const token = await this.identityApi.getIdToken(); - return { - headers: { - authorization: `Bearer ${token}`, - }, - }; - } catch (e) { - return {}; - } + const token = await this.identityApi.getIdToken(); + return { + headers: { + authorization: `Bearer ${token}`, + }, + }; } } From a57927f5d5a41b70b2d90f4f889c65a6f22003a1 Mon Sep 17 00:00:00 2001 From: Matto Date: Wed, 27 Oct 2021 20:33:33 +1100 Subject: [PATCH 14/65] Removed dependency, introduced isValidUrl, and reinstated `configPaths` Signed-off-by: Matto --- packages/backend-common/package.json | 1 - packages/backend-common/src/config.ts | 3 +- packages/backend-common/src/urls.test.ts | 34 ++++++++++++++ packages/backend-common/src/urls.ts | 25 +++++++++++ packages/cli/package.json | 1 - packages/cli/src/lib/config.ts | 3 +- packages/cli/src/lib/urls.test.ts | 34 ++++++++++++++ packages/cli/src/lib/urls.ts | 25 +++++++++++ packages/config-loader/api-report.md | 1 + packages/config-loader/package.json | 1 - packages/config-loader/src/lib/index.ts | 1 + packages/config-loader/src/lib/urls.test.ts | 34 ++++++++++++++ packages/config-loader/src/lib/urls.ts | 25 +++++++++++ packages/config-loader/src/loader.test.ts | 49 ++++++++++++++++++++- packages/config-loader/src/loader.ts | 14 +++++- yarn.lock | 37 ++-------------- 16 files changed, 247 insertions(+), 41 deletions(-) create mode 100644 packages/backend-common/src/urls.test.ts create mode 100644 packages/backend-common/src/urls.ts create mode 100644 packages/cli/src/lib/urls.test.ts create mode 100644 packages/cli/src/lib/urls.ts create mode 100644 packages/config-loader/src/lib/urls.test.ts create mode 100644 packages/config-loader/src/lib/urls.ts diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 67a2c1a853..0c368d7805 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -33,7 +33,6 @@ "@backstage/config": "^0.1.10", "@backstage/config-loader": "^0.6.8", "@backstage/errors": "^0.1.2", - "@backstage/integration": "^0.6.5", "@google-cloud/storage": "^5.8.0", "@octokit/rest": "^18.5.3", "@types/cors": "^2.8.6", diff --git a/packages/backend-common/src/config.ts b/packages/backend-common/src/config.ts index afdb4fd0f7..c93f2cedf9 100644 --- a/packages/backend-common/src/config.ts +++ b/packages/backend-common/src/config.ts @@ -20,7 +20,7 @@ import { Logger } from 'winston'; import { findPaths } from '@backstage/cli-common'; import { Config, ConfigReader, JsonValue } from '@backstage/config'; import { ConfigTarget, loadConfig } from '@backstage/config-loader'; -import { isValidUrl } from '@backstage/integration'; +import { isValidUrl } from './urls'; class ObservableConfigProxy implements Config { private config: Config = new ConfigReader({}); @@ -130,6 +130,7 @@ export async function loadBackendConfig(options: { const configs = await loadConfig({ configRoot: paths.targetRoot, + configPaths: [], configTargets: configTargets, watch: { onChange(newConfigs) { diff --git a/packages/backend-common/src/urls.test.ts b/packages/backend-common/src/urls.test.ts new file mode 100644 index 0000000000..c2a67fb849 --- /dev/null +++ b/packages/backend-common/src/urls.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2021 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 { isValidUrl } from './urls'; + +describe('isValidUrl', () => { + it('should return true for url', () => { + const validUrl = isValidUrl('http://some.valid.url'); + expect(validUrl).toBe(true); + }); + + it('should return false for absolute path', () => { + const validUrl = isValidUrl('/some/absolute/path'); + expect(validUrl).toBe(false); + }); + + it('should return false for relative path', () => { + const validUrl = isValidUrl('../some/relative/path'); + expect(validUrl).toBe(false); + }); +}); diff --git a/packages/backend-common/src/urls.ts b/packages/backend-common/src/urls.ts new file mode 100644 index 0000000000..848cea25d9 --- /dev/null +++ b/packages/backend-common/src/urls.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2021 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 function isValidUrl(url: string): boolean { + try { + // eslint-disable-next-line no-new + new URL(url); + return true; + } catch { + return false; + } +} diff --git a/packages/cli/package.json b/packages/cli/package.json index 519b45a5ad..0dc4dc3cf7 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -31,7 +31,6 @@ "@babel/core": "^7.4.4", "@babel/plugin-transform-modules-commonjs": "^7.4.4", "@backstage/cli-common": "^0.1.3", - "@backstage/integration": "^0.6.5", "@backstage/config": "^0.1.10", "@backstage/config-loader": "^0.6.8", "@hot-loader/react-dom": "^16.13.0", diff --git a/packages/cli/src/lib/config.ts b/packages/cli/src/lib/config.ts index 218771beb6..013328d9f3 100644 --- a/packages/cli/src/lib/config.ts +++ b/packages/cli/src/lib/config.ts @@ -21,7 +21,7 @@ import { } from '@backstage/config-loader'; import { ConfigReader } from '@backstage/config'; import { paths } from './paths'; -import { isValidUrl } from '@backstage/integration'; +import { isValidUrl } from './urls'; type Options = { args: string[]; @@ -56,6 +56,7 @@ export async function loadCliConfig(options: Options) { ? async name => process.env[name] || 'x' : undefined, configRoot: paths.targetRoot, + configPaths: [], configTargets: configTargets, }); diff --git a/packages/cli/src/lib/urls.test.ts b/packages/cli/src/lib/urls.test.ts new file mode 100644 index 0000000000..c2a67fb849 --- /dev/null +++ b/packages/cli/src/lib/urls.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2021 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 { isValidUrl } from './urls'; + +describe('isValidUrl', () => { + it('should return true for url', () => { + const validUrl = isValidUrl('http://some.valid.url'); + expect(validUrl).toBe(true); + }); + + it('should return false for absolute path', () => { + const validUrl = isValidUrl('/some/absolute/path'); + expect(validUrl).toBe(false); + }); + + it('should return false for relative path', () => { + const validUrl = isValidUrl('../some/relative/path'); + expect(validUrl).toBe(false); + }); +}); diff --git a/packages/cli/src/lib/urls.ts b/packages/cli/src/lib/urls.ts new file mode 100644 index 0000000000..848cea25d9 --- /dev/null +++ b/packages/cli/src/lib/urls.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2021 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 function isValidUrl(url: string): boolean { + try { + // eslint-disable-next-line no-new + new URL(url); + return true; + } catch { + return false; + } +} diff --git a/packages/config-loader/api-report.md b/packages/config-loader/api-report.md index 1dad108c38..c8e01f27b0 100644 --- a/packages/config-loader/api-report.md +++ b/packages/config-loader/api-report.md @@ -46,6 +46,7 @@ export function loadConfig(options: LoadConfigOptions): Promise; // @public (undocumented) export type LoadConfigOptions = { configRoot: string; + configPaths: string[]; configTargets: ConfigTarget[]; env?: string; experimentalEnvFunc?: EnvFunc; diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 4b5afb3d29..9475058b24 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -30,7 +30,6 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/integration": "^0.6.5", "@backstage/cli-common": "^0.1.3", "@backstage/config": "^0.1.9", "@types/json-schema": "^7.0.6", diff --git a/packages/config-loader/src/lib/index.ts b/packages/config-loader/src/lib/index.ts index 32a0191cae..ca88b771ba 100644 --- a/packages/config-loader/src/lib/index.ts +++ b/packages/config-loader/src/lib/index.ts @@ -17,3 +17,4 @@ export { readEnvConfig } from './env'; export * from './transform'; export * from './schema'; +export { isValidUrl } from './urls'; diff --git a/packages/config-loader/src/lib/urls.test.ts b/packages/config-loader/src/lib/urls.test.ts new file mode 100644 index 0000000000..c2a67fb849 --- /dev/null +++ b/packages/config-loader/src/lib/urls.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2021 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 { isValidUrl } from './urls'; + +describe('isValidUrl', () => { + it('should return true for url', () => { + const validUrl = isValidUrl('http://some.valid.url'); + expect(validUrl).toBe(true); + }); + + it('should return false for absolute path', () => { + const validUrl = isValidUrl('/some/absolute/path'); + expect(validUrl).toBe(false); + }); + + it('should return false for relative path', () => { + const validUrl = isValidUrl('../some/relative/path'); + expect(validUrl).toBe(false); + }); +}); diff --git a/packages/config-loader/src/lib/urls.ts b/packages/config-loader/src/lib/urls.ts new file mode 100644 index 0000000000..848cea25d9 --- /dev/null +++ b/packages/config-loader/src/lib/urls.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2021 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 function isValidUrl(url: string): boolean { + try { + // eslint-disable-next-line no-new + new URL(url); + return true; + } catch { + return false; + } +} diff --git a/packages/config-loader/src/loader.test.ts b/packages/config-loader/src/loader.test.ts index e8b5a8828b..804254afa0 100644 --- a/packages/config-loader/src/loader.test.ts +++ b/packages/config-loader/src/loader.test.ts @@ -67,6 +67,13 @@ describe('loadConfig', () => { $file: secrets/session-key.txt escaped: \$\${Escaped} `, + '/root/app-config2.yaml': ` + app: + title: Example App 2 + sessionKey: + $file: secrets/session-key.txt + escaped: \$\${Escaped} + `, '/root/app-config.development.yaml': ` app: sessionKey: development-key @@ -111,6 +118,7 @@ describe('loadConfig', () => { await expect( loadConfig({ configRoot: '/root', + configPaths: [], configTargets: [], env: 'production', }), @@ -136,6 +144,7 @@ describe('loadConfig', () => { await expect( loadConfig({ configRoot: '/root', + configPaths: [], configTargets: [{ url: configUrl }], env: 'production', remote: { @@ -156,10 +165,43 @@ describe('loadConfig', () => { ]); }); - it('loads config with secrets', async () => { + it('loads config with secrets from two different files', async () => { await expect( loadConfig({ configRoot: '/root', + configPaths: ['/root/app-config2.yaml'], + configTargets: [{ path: '/root/app-config.yaml' }], + env: 'production', + }), + ).resolves.toEqual([ + { + context: 'app-config.yaml', + data: { + app: { + title: 'Example App', + sessionKey: 'abc123', + escaped: '${Escaped}', + }, + }, + }, + { + context: 'app-config2.yaml', + data: { + app: { + title: 'Example App 2', + sessionKey: 'abc123', + escaped: '${Escaped}', + }, + }, + }, + ]); + }); + + it('loads config with secrets from single file', async () => { + await expect( + loadConfig({ + configRoot: '/root', + configPaths: ['/root/app-config.yaml'], configTargets: [{ path: '/root/app-config.yaml' }], env: 'production', }), @@ -181,6 +223,7 @@ describe('loadConfig', () => { await expect( loadConfig({ configRoot: '/root', + configPaths: [], configTargets: [ { path: '/root/app-config.yaml' }, { path: '/root/app-config.development.yaml' }, @@ -221,6 +264,7 @@ describe('loadConfig', () => { await expect( loadConfig({ configRoot: '/root', + configPaths: [], configTargets: [{ path: '/root/app-config.substitute.yaml' }], env: 'development', }), @@ -246,6 +290,7 @@ describe('loadConfig', () => { await expect( loadConfig({ configRoot: '/root', + configPaths: [], configTargets: [], watch: { onChange: onChange.resolve, @@ -294,6 +339,7 @@ describe('loadConfig', () => { await expect( loadConfig({ configRoot: '/root', + configPaths: [], configTargets: [{ url: configUrl }], watch: { onChange: onChange.resolve, @@ -339,6 +385,7 @@ describe('loadConfig', () => { await loadConfig({ configRoot: '/root', + configPaths: [], configTargets: [], watch: { onChange: () => { diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index 2e7a0df801..1f20c4e437 100644 --- a/packages/config-loader/src/loader.ts +++ b/packages/config-loader/src/loader.ts @@ -24,10 +24,10 @@ import { createIncludeTransform, createSubstitutionTransform, EnvFunc, + isValidUrl, readEnvConfig, } from './lib'; import fetch from 'node-fetch'; -import { isValidUrl } from '@backstage/integration'; export type ConfigTarget = { path: string } | { url: string }; @@ -55,6 +55,11 @@ export type LoadConfigOptions = { // The root directory of the config loading context. Used to find default configs. configRoot: string; + /** Absolute paths to load config files from. Configs from earlier paths have lower priority. + * @deprecated Use {@link configTargets} instead. + */ + configPaths: string[]; + // Paths to load config files from. Configs from earlier paths have lower priority. configTargets: ConfigTarget[]; @@ -94,6 +99,13 @@ export async function loadConfig( .filter((e): e is { path: string } => e.hasOwnProperty('path')) .map(configTarget => configTarget.path); + // Append deprecated configPaths to the absolute config paths received via configTargets. + options.configPaths.forEach(cp => { + if (!configPaths.includes(cp)) { + configPaths.push(cp); + } + }); + const configUrls: string[] = options.configTargets .slice() .filter((e): e is { url: string } => e.hasOwnProperty('url')) diff --git a/yarn.lock b/yarn.lock index 00c91f7783..10c65d5df7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11336,11 +11336,6 @@ core-js@^2.5.0: resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== -core-js@^3.0.0: - version "3.18.3" - resolved "https://registry.npmjs.org/core-js/-/core-js-3.18.3.tgz#86a0bba2d8ec3df860fefcc07a8d119779f01509" - integrity sha512-tReEhtMReZaPFVw7dajMx0vlsz3oOb8ajgPoHVYGxr8ErnZ6PcYEvvmjGmXlfpnxpkYSdOQttjB+MvVbCGfvLw== - core-js@^3.0.4, core-js@^3.6.5, core-js@^3.8.2: version "3.15.0" resolved "https://registry.npmjs.org/core-js/-/core-js-3.15.0.tgz#db9554ebce0b6fd90dc9b1f2465c841d2d055044" @@ -14042,22 +14037,6 @@ fetch-blob@2.1.2: resolved "https://registry.npmjs.org/fetch-blob/-/fetch-blob-2.1.2.tgz#a7805db1361bd44c1ef62bb57fb5fe8ea173ef3c" integrity sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow== -fetch-mock@^9.11.0: - version "9.11.0" - resolved "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.11.0.tgz#371c6fb7d45584d2ae4a18ee6824e7ad4b637a3f" - integrity sha512-PG1XUv+x7iag5p/iNHD4/jdpxL9FtVSqRMUQhPab4hVDt80T1MH5ehzVrL2IdXO9Q2iBggArFvPqjUbHFuI58Q== - dependencies: - "@babel/core" "^7.0.0" - "@babel/runtime" "^7.0.0" - core-js "^3.0.0" - debug "^4.1.1" - glob-to-regexp "^0.4.0" - is-subset "^0.1.1" - lodash.isequal "^4.5.0" - path-to-regexp "^2.2.1" - querystring "^0.2.0" - whatwg-url "^6.5.0" - fetch-readablestream@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/fetch-readablestream/-/fetch-readablestream-0.2.0.tgz#eaa6d1a76b12de2d4731a343393c6ccdcfe2c795" @@ -14888,7 +14867,7 @@ glob-to-regexp@^0.3.0: resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= -glob-to-regexp@^0.4.0, glob-to-regexp@^0.4.1: +glob-to-regexp@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== @@ -16872,11 +16851,6 @@ is-subdir@^1.1.1: dependencies: better-path-resolve "1.0.0" -is-subset@^0.1.1: - version "0.1.1" - resolved "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" - integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY= - is-svg@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" @@ -18745,7 +18719,7 @@ lodash.isempty@^4.4.0: resolved "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" integrity sha1-b4bL7di+TsmHvpqvM8loTbGzHn4= -lodash.isequal@^4.0.0, lodash.isequal@^4.5.0: +lodash.isequal@^4.0.0: version "4.5.0" resolved "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= @@ -21559,11 +21533,6 @@ path-to-regexp@^1.7.0: dependencies: isarray "0.0.1" -path-to-regexp@^2.2.1: - version "2.4.0" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704" - integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w== - path-type@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -28010,7 +27979,7 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" -whatwg-url@^6.4.1, whatwg-url@^6.5.0: +whatwg-url@^6.4.1: version "6.5.0" resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ== From 021986e8a3f164fece31eaa38b7f2cc9d312252a Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Wed, 27 Oct 2021 13:08:57 +0200 Subject: [PATCH 15/65] fixed route resolving Signed-off-by: Alex Rybchenko --- .changeset/real-mails-add.md | 6 ++++++ .../src/components/TabbedLayout/RoutedTabs.tsx | 9 +++++++-- .../catalog/src/components/EntityLayout/EntityLayout.tsx | 4 +++- 3 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 .changeset/real-mails-add.md diff --git a/.changeset/real-mails-add.md b/.changeset/real-mails-add.md new file mode 100644 index 0000000000..15cdc2997f --- /dev/null +++ b/.changeset/real-mails-add.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-components': patch +'@backstage/plugin-catalog': patch +--- + +fixed route resolving diff --git a/packages/core-components/src/components/TabbedLayout/RoutedTabs.tsx b/packages/core-components/src/components/TabbedLayout/RoutedTabs.tsx index 2a65c8ffc8..d0c36e286c 100644 --- a/packages/core-components/src/components/TabbedLayout/RoutedTabs.tsx +++ b/packages/core-components/src/components/TabbedLayout/RoutedTabs.tsx @@ -33,9 +33,14 @@ export function useSelectedSubRoute(subRoutes: SubRoute[]): { element: children, })); - const element = useRoutes(routes) ?? subRoutes[0].children; + // TODO: remove once react-router updated + const sortedRoutes = routes.sort((a, b) => + b.path.slice(0, -2).localeCompare(a.path.slice(0, -2)), + ); - const [matchedRoute] = matchRoutes(routes, `/${params['*']}`) ?? []; + const element = useRoutes(sortedRoutes) ?? subRoutes[0].children; + + const [matchedRoute] = matchRoutes(sortedRoutes, `/${params['*']}`) ?? []; const foundIndex = matchedRoute ? subRoutes.findIndex(t => `${t.path}/*` === matchedRoute.route.path) : 0; diff --git a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx index c5679355e5..76619b652b 100644 --- a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx +++ b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx @@ -200,7 +200,9 @@ export const EntityLayout = ({ tabProps: props.tabProps, }, ]; - }), + }) + // TODO: remove once react-router updated + .sort((a, b) => b.path.localeCompare(a.path)), [entity], ); From 965117dab87e2314765450b113296f8fe5429f7f Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Wed, 27 Oct 2021 14:48:24 +0200 Subject: [PATCH 16/65] remove wrong sorting Signed-off-by: Alex Rybchenko --- plugins/catalog/src/components/EntityLayout/EntityLayout.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx index 76619b652b..c5679355e5 100644 --- a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx +++ b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx @@ -200,9 +200,7 @@ export const EntityLayout = ({ tabProps: props.tabProps, }, ]; - }) - // TODO: remove once react-router updated - .sort((a, b) => b.path.localeCompare(a.path)), + }), [entity], ); From ab451ef2274fc5ea56124d4f08870d0f3990a832 Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Wed, 27 Oct 2021 15:20:48 +0200 Subject: [PATCH 17/65] updated RoutedTabs.test Signed-off-by: Alex Rybchenko --- .../components/TabbedLayout/RoutedTabs.test.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/core-components/src/components/TabbedLayout/RoutedTabs.test.tsx b/packages/core-components/src/components/TabbedLayout/RoutedTabs.test.tsx index 9b33304a95..bbd48c127c 100644 --- a/packages/core-components/src/components/TabbedLayout/RoutedTabs.test.tsx +++ b/packages/core-components/src/components/TabbedLayout/RoutedTabs.test.tsx @@ -31,6 +31,12 @@ const testRoute2 = { children:
tabbed-test-content-2
, }; +const testRoute3 = { + title: 'tabbed-test-title-3', + path: '/some-other-path-similar', + children:
tabbed-test-content-3
, +}; + describe('RoutedTabs', () => { it('renders simplest case', async () => { const rendered = await renderInTestApp( @@ -46,7 +52,7 @@ describe('RoutedTabs', () => { } + element={} /> , ); @@ -61,6 +67,13 @@ describe('RoutedTabs', () => { expect(rendered.getByText('tabbed-test-title-2')).toBeInTheDocument(); expect(rendered.queryByText('tabbed-test-content-2')).toBeInTheDocument(); + + const thirdTab = rendered.queryAllByRole('tab')[2]; + act(() => { + fireEvent.click(thirdTab); + }); + expect(rendered.getByText('tabbed-test-title-3')).toBeInTheDocument(); + expect(rendered.queryByText('tabbed-test-content-3')).toBeInTheDocument(); }); describe('correctly delegates nested links', () => { From 7205d37a142e5814518feee3cfb7046342e194f3 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Thu, 28 Oct 2021 13:04:14 +0100 Subject: [PATCH 18/65] Updated script to create, update and close github issues Signed-off-by: Harry Hogg --- .../workflows/snyk-github-issue-creator.yml | 27 --- .github/workflows/snyk-monitor.yml | 5 + package.json | 3 +- scripts/snyk-github-issue-sync.ts | 182 +++++++++++------- 4 files changed, 124 insertions(+), 93 deletions(-) delete mode 100644 .github/workflows/snyk-github-issue-creator.yml diff --git a/.github/workflows/snyk-github-issue-creator.yml b/.github/workflows/snyk-github-issue-creator.yml deleted file mode 100644 index ad50eef0cc..0000000000 --- a/.github/workflows/snyk-github-issue-creator.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Create and Update Github Issues from Snyk report - -on: - [push, pull_request] - # workflow_dispatch: - # pull_request: - # schedule: - # - cron: '0 */4 * * *' # every 4 hours - -jobs: - sync: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Run Snyk to check for vulnerabilities - uses: snyk/actions/node@master - continue-on-error: - env: - SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - with: - args: --yarn-workspaces --strict-out-of-sync=false - json: true - - - name: Run the Snyk Github Issue command - run: yarn ts-node scripts/snyk-github-issue-sync.ts diff --git a/.github/workflows/snyk-monitor.yml b/.github/workflows/snyk-monitor.yml index 0adb6c5bcb..32df37f35b 100644 --- a/.github/workflows/snyk-monitor.yml +++ b/.github/workflows/snyk-monitor.yml @@ -43,9 +43,14 @@ jobs: --org=backstage-dgh --strict-out-of-sync=false --sarif-file-output=snyk.sarif + --json-file-output=snyk.json + json: true env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - name: Upload Snyk report uses: github/codeql-action/upload-sarif@v1 with: sarif_file: snyk.sarif + + - name: Update Github issues + run: yarn ts-node scripts/snyk-github-issue-sync.ts diff --git a/package.json b/package.json index 77ec43ee24..94de50e792 100644 --- a/package.json +++ b/package.json @@ -59,8 +59,8 @@ "@microsoft/tsdoc": "^0.13.2" }, "devDependencies": { - "@octokit/rest": "^18.12.0", "@changesets/cli": "^2.14.0", + "@octokit/rest": "^18.12.0", "@spotify/prettier-config": "^11.0.0", "@types/webpack": "^5.28.0", "command-exists": "^1.2.9", @@ -70,6 +70,7 @@ "husky": "^6.0.0", "lerna": "^4.0.0", "lint-staged": "^11.1.2", + "minimist": "^1.2.5", "prettier": "^2.2.1", "shx": "^0.3.2", "yarn-lock-check": "^1.0.5" diff --git a/scripts/snyk-github-issue-sync.ts b/scripts/snyk-github-issue-sync.ts index ed5082717d..d7f64cf1ff 100644 --- a/scripts/snyk-github-issue-sync.ts +++ b/scripts/snyk-github-issue-sync.ts @@ -13,46 +13,65 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -// eslint-disable-next-line import/no-extraneous-dependencies +/* eslint-disable import/no-extraneous-dependencies */ import { Octokit } from '@octokit/rest'; +import minimist from 'minimist'; // Generated by GitHub workflow .github/workflows/snyk-github-issue-creator import synkJsonOutput from '../snyk.json'; -// Pattern for a GitHub Issue title -// Snyk vulnerability [Vulnerability ID] - type Vulnerability = { description: string; + packages: { + name: string; + target: string; + }[]; snykId: string; - packages: Set; }; -// Remember to fix me! -const GH_OWNER = 'orkohunter'; +const argv = minimist(process.argv.slice(2)); + +const GH_OWNER = 'backstage'; const GH_REPO = 'backstage'; +const SNYK_GH_LABEL = 'snyk-vulnerability'; +const SNYK_ID_REGEX = /\[([A-Z0-9-:]+)]/i; + +const isDryRun = 'dryrun' in argv; + +if (!process.env.GITHUB_TOKEN) { + console.error('GITHUB_TOKEN is not set. Please provide a Github token'); + process.exit(1); +} const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN, }); +if (isDryRun) { + console.log( + '⚠️ Running in dryrun mode, no issues will be updated on Github ⚠️', + ); +} + const fetchSnykGithubIssueMap = async (): Promise> => { const snykGithubIssueMap: Record = {}; const iterator = octokit.paginate.iterator(octokit.rest.issues.listForRepo, { - // TODO(Harry/Himanshu): Use a CLI flag for these values. owner: GH_OWNER, repo: GH_REPO, per_page: 100, - labels: 'snyk-vulnerability', + state: 'open', + labels: SNYK_GH_LABEL, }); for await (const { data: issues } of iterator) { for (const issue of issues) { // Gets the Vulnerability ID from square braces - const match = /\([([A-Z0-9-]+)\])/.exec(issue.title); + const match = SNYK_ID_REGEX.exec(issue.title); if (match && match[1]) { - snykGithubIssueMap[match[1]] = issue.id; + snykGithubIssueMap[match[1]] = issue.number; + } else { + console.log(`Unmatched Snyk ID for ${issue.title}`); } } } @@ -60,86 +79,119 @@ const fetchSnykGithubIssueMap = async (): Promise> => { return snykGithubIssueMap; }; -const generateIssueBody = (vulnerability: Vulnerability) => { - let issueBody = ''; - issueBody += '## Affecting Packages/Plugins\n'; - vulnerability.packages.forEach(pkgName => { - issueBody += `* ${pkgName}\n`; - }); - // TODO: Use displayTargetFile in snyk.json to create hyperlinks - issueBody += '\n'; - issueBody += vulnerability.description; - return issueBody; -}; +const generateIssueBody = (vulnerability: Vulnerability) => ` +## Affecting Packages/Plugins -const createGithubIssue = (vulnerability: Vulnerability) => { +${Array.from(vulnerability.packages).map( + ({ name, target }) => `* [${name}](${target})\n`, +)} + +${vulnerability.description} +`; + +const createGithubIssue = async (vulnerability: Vulnerability) => { console.log( - `Create issue for vulnerability ${ - vulnerability.snykId - } affecting packages ${Array.from(vulnerability.packages)}`, + `Create Github Issue for Snyk Vulnerability ${vulnerability.snykId}`, ); - octokit.issues.create({ - owner: GH_OWNER, - repo: GH_REPO, - title: `Snyk vulnerability [${vulnerability.snykId}]`, - labels: ['snyk-vulnerability', 'help wanted'], - body: generateIssueBody(vulnerability), + + vulnerability.packages.forEach(({ name, target }) => { + console.log(`- ${name} [${target}]`); }); + + if (!isDryRun) { + await octokit.issues.create({ + owner: GH_OWNER, + repo: GH_REPO, + title: `Snyk vulnerability [${vulnerability.snykId}]`, + labels: [SNYK_GH_LABEL, 'help wanted'], + body: generateIssueBody(vulnerability), + }); + } }; -const updateGithubIssue = ( +const updateGithubIssue = async ( githubIssueId: number, vulnerability: Vulnerability, ) => { console.log( - `Update issue ${githubIssueId} for vulnerability ${vulnerability.snykId}`, + `Update Github Issue #${githubIssueId} for Snky Vulnerability ${vulnerability.snykId}`, ); - // TODO(hhogg): Update github issue with the contents from a Snyk issue. + + if (!isDryRun) { + await octokit.issues.update({ + owner: GH_OWNER, + repo: GH_REPO, + issue_number: githubIssueId, + body: generateIssueBody(vulnerability), + }); + } }; -const closeGithubIssue = (githubIssueId: number) => { - console.log(`Delete issue ${githubIssueId}`); - // TODO(hhogg): Delete a github issue +const closeGithubIssue = async (githubIssueId: number) => { + console.log(`Closing Github Issue #${githubIssueId}`); + + if (!isDryRun) { + await octokit.issues.update({ + owner: GH_OWNER, + repo: GH_REPO, + issue_number: githubIssueId, + state: 'closed', + }); + } }; async function main() { const snykGithubIssueMap = await fetchSnykGithubIssueMap(); const vulnerabilityStore: Record = {}; - // Group the Snyk vulnerabilities, and aggregate the affecting packages. - synkJsonOutput.forEach(({ projectName, vulnerabilities }) => { - vulnerabilities.forEach( - ({ id, description }: { id: string; description: string }) => { - if (id !== undefined && description !== undefined) { - if (vulnerabilityStore[id]) { - vulnerabilityStore[id].packages.add(projectName); - } else { - vulnerabilityStore[id] = { - description, - snykId: id, - packages: new Set([projectName]), - }; + // Group the Snyk vulnerabilities, and link back to the affecting packages. + synkJsonOutput.forEach( + ({ projectName, displayTargetFile, vulnerabilities }) => { + vulnerabilities.forEach( + ({ id, description }: { id: string; description: string }) => { + if (id !== undefined && description !== undefined) { + if (vulnerabilityStore[id]) { + if ( + !vulnerabilityStore[id].packages.some( + ({ name }) => name === projectName, + ) + ) { + vulnerabilityStore[id].packages.push({ + name: projectName, + target: displayTargetFile, + }); + } + } else { + vulnerabilityStore[id] = { + description, + snykId: id, + packages: [ + { + name: projectName, + target: displayTargetFile, + }, + ], + }; + } } - } - }, - ); - }); + }, + ); + }, + ); - // Loop over the grouped vulnerabilities and create/update accordingly - Object.entries(vulnerabilityStore).forEach(([id, vulnerability]) => { + for (const [id, vulnerability] of Object.entries(vulnerabilityStore)) { if (snykGithubIssueMap[id]) { - updateGithubIssue(snykGithubIssueMap[id], vulnerability); + await updateGithubIssue(snykGithubIssueMap[id], vulnerability); } else { - createGithubIssue(vulnerability); + await createGithubIssue(vulnerability); } - }); + } - // Loop over the Github issues and delete accordingly. - Object.entries(snykGithubIssueMap).forEach(([snykId, githubIssueId]) => { - if (!snykGithubIssueMap[snykId]) { - closeGithubIssue(githubIssueId); + for (const [snykId, githubIssueId] of Object.entries(snykGithubIssueMap)) { + if (!vulnerabilityStore[snykId]) { + await closeGithubIssue(githubIssueId); } - }); + } } main().catch(error => { From 5d4053c827ffc307bbeb3dc2ded8da137aee3b58 Mon Sep 17 00:00:00 2001 From: Matto Date: Mon, 27 Sep 2021 23:41:24 +1000 Subject: [PATCH 19/65] Read config from remote config server Signed-off-by: Matto --- .changeset/giant-years-help.md | 8 + packages/backend-common/src/config.ts | 10 +- packages/cli/package.json | 6 + packages/cli/src/lib/config.ts | 16 +- packages/config-loader/api-report.md | 34 +++- packages/config-loader/package.json | 10 +- packages/config-loader/src/index.ts | 2 +- packages/config-loader/src/loader.test.ts | 125 +++++++++++- packages/config-loader/src/loader.ts | 236 +++++++++++++++++++--- packages/integration/api-report.md | 5 + packages/integration/src/index.ts | 1 + yarn.lock | 74 ++++++- 12 files changed, 475 insertions(+), 52 deletions(-) create mode 100644 .changeset/giant-years-help.md diff --git a/.changeset/giant-years-help.md b/.changeset/giant-years-help.md new file mode 100644 index 0000000000..f0ca12ef19 --- /dev/null +++ b/.changeset/giant-years-help.md @@ -0,0 +1,8 @@ +--- +'@backstage/backend-common': patch +'@backstage/cli': patch +'@backstage/config-loader': patch +'@backstage/integration': patch +--- + +Reading app config from a remote server diff --git a/packages/backend-common/src/config.ts b/packages/backend-common/src/config.ts index fa49d5f015..90131ebbe7 100644 --- a/packages/backend-common/src/config.ts +++ b/packages/backend-common/src/config.ts @@ -20,7 +20,8 @@ import { Logger } from 'winston'; import { findPaths } from '@backstage/cli-common'; import { Config, ConfigReader } from '@backstage/config'; import { JsonValue } from '@backstage/types'; -import { loadConfig } from '@backstage/config-loader'; +import { ConfigTarget, loadConfig } from '@backstage/config-loader'; +import { isValidUrl } from '@backstage/integration'; export class ObservableConfigProxy implements Config { private config: Config = new ConfigReader({}); @@ -149,7 +150,10 @@ export async function loadBackendConfig(options: { argv: string[]; }): Promise { const args = parseArgs(options.argv); - const configPaths: string[] = [args.config ?? []].flat(); + + const configTargets: ConfigTarget[] = [args.config ?? []] + .flat() + .map(arg => (isValidUrl(arg) ? { url: arg } : { path: resolvePath(arg) })); const config = new ObservableConfigProxy(options.logger); @@ -158,7 +162,7 @@ export async function loadBackendConfig(options: { const configs = await loadConfig({ configRoot: paths.targetRoot, - configPaths: configPaths.map(opt => resolvePath(opt)), + configTargets: configTargets, watch: { onChange(newConfigs) { options.logger.info( diff --git a/packages/cli/package.json b/packages/cli/package.json index 62a008087e..d85adff199 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -29,10 +29,16 @@ }, "dependencies": { "@backstage/cli-common": "^0.1.4", + "@babel/core": "^7.4.4", + "@babel/plugin-transform-modules-commonjs": "^7.4.4", + "@backstage/cli-common": "^0.1.3", + "@backstage/integration": "^0.6.5", "@backstage/config": "^0.1.10", "@backstage/config-loader": "^0.7.0", "@backstage/errors": "^0.1.3", "@backstage/types": "^0.1.1", + "@backstage/config-loader": "^0.6.8", + "@backstage/backend-common": "^0.9.4", "@hot-loader/react-dom": "^16.13.0", "@lerna/package-graph": "^4.0.0", "@lerna/project": "^4.0.0", diff --git a/packages/cli/src/lib/config.ts b/packages/cli/src/lib/config.ts index 3688d60089..79d30ee220 100644 --- a/packages/cli/src/lib/config.ts +++ b/packages/cli/src/lib/config.ts @@ -14,9 +14,14 @@ * limitations under the License. */ -import { loadConfig, loadConfigSchema } from '@backstage/config-loader'; +import { + ConfigTarget, + loadConfig, + loadConfigSchema, +} from '@backstage/config-loader'; import { ConfigReader } from '@backstage/config'; import { paths } from './paths'; +import { isValidUrl } from '@backstage/integration'; type Options = { args: string[]; @@ -26,7 +31,12 @@ type Options = { }; export async function loadCliConfig(options: Options) { - const configPaths = options.args.map(arg => paths.resolveTarget(arg)); + const configTargets: ConfigTarget[] = []; + options.args.forEach(arg => { + if (!isValidUrl(arg)) { + configTargets.push({ path: paths.resolveTarget(arg) }); + } + }); // Consider all packages in the monorepo when loading in config const { Project } = require('@lerna/project'); @@ -48,7 +58,7 @@ export async function loadCliConfig(options: Options) { ? async name => process.env[name] || 'x' : undefined, configRoot: paths.targetRoot, - configPaths, + configTargets: configTargets, }); // printing to stderr to not clobber stdout in case the cli command diff --git a/packages/config-loader/api-report.md b/packages/config-loader/api-report.md index 19b5ee9e27..4ed409e3ce 100644 --- a/packages/config-loader/api-report.md +++ b/packages/config-loader/api-report.md @@ -23,6 +23,17 @@ export type ConfigSchemaProcessingOptions = { withFilteredKeys?: boolean; }; +// Warning: (ae-missing-release-tag) "ConfigTarget" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ConfigTarget = + | { + path: string; + } + | { + url: string; + }; + // @public export type ConfigVisibility = 'frontend' | 'backend' | 'secret'; @@ -32,13 +43,11 @@ export function loadConfig(options: LoadConfigOptions): Promise; // @public export type LoadConfigOptions = { configRoot: string; - configPaths: string[]; + configTargets: ConfigTarget[]; env?: string; experimentalEnvFunc?: (name: string) => Promise; - watch?: { - onChange: (configs: AppConfig[]) => void; - stopSignal?: Promise; - }; + remote?: Remote; + watch?: Watch; }; // @public @@ -64,6 +73,13 @@ export function readEnvConfig(env: { [name: string]: string | undefined; }): AppConfig[]; +// Warning: (ae-missing-release-tag) "Remote" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type Remote = { + reloadIntervalSeconds: number; +}; + // @public export type TransformFunc = ( value: T, @@ -71,4 +87,12 @@ export type TransformFunc = ( visibility: ConfigVisibility; }, ) => T | undefined; + +// Warning: (ae-missing-release-tag) "Watch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type Watch = { + onChange: (configs: AppConfig[]) => void; + stopSignal?: Promise; +}; ``` diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index a731225e2b..cab3a43fc5 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -30,10 +30,12 @@ "clean": "backstage-cli clean" }, "dependencies": { + "@backstage/integration": "^0.6.5", "@backstage/cli-common": "^0.1.4", "@backstage/config": "^0.1.9", "@backstage/errors": "^0.1.3", "@backstage/types": "^0.1.1", + "@backstage/backend-common": "^0.9.4", "@types/json-schema": "^7.0.6", "ajv": "^7.0.3", "chokidar": "^3.5.2", @@ -42,8 +44,10 @@ "json-schema-merge-allof": "^0.8.1", "json-schema-traverse": "^1.0.0", "typescript-json-schema": "^0.50.1", + "uuid": "^8.3.2", "yaml": "^1.9.2", - "yup": "^0.32.9" + "yup": "^0.32.9", + "node-fetch": "2.6.5" }, "devDependencies": { "@types/jest": "^26.0.7", @@ -51,7 +55,9 @@ "@types/mock-fs": "^4.10.0", "@types/node": "^14.14.32", "@types/yup": "^0.29.13", - "mock-fs": "^5.1.0" + "mock-fs": "^5.1.0", + "fetch-mock-jest": "1.5.1", + "fetch-mock": "^9.11.0" }, "files": [ "dist" diff --git a/packages/config-loader/src/index.ts b/packages/config-loader/src/index.ts index 888c68ef70..ee3e6413ad 100644 --- a/packages/config-loader/src/index.ts +++ b/packages/config-loader/src/index.ts @@ -29,4 +29,4 @@ export type { TransformFunc, } from './lib'; export { loadConfig } from './loader'; -export type { LoadConfigOptions } from './loader'; +export type { ConfigTarget, LoadConfigOptions, Watch, Remote } from './loader'; diff --git a/packages/config-loader/src/loader.test.ts b/packages/config-loader/src/loader.test.ts index 3f51e76936..190904a870 100644 --- a/packages/config-loader/src/loader.test.ts +++ b/packages/config-loader/src/loader.test.ts @@ -18,12 +18,33 @@ import { AppConfig } from '@backstage/config'; import { loadConfig } from './loader'; import mockFs from 'mock-fs'; import fs from 'fs-extra'; +import { v4 as uuidv4 } from 'uuid'; + +const fetchMock = require('fetch-mock').sandbox(); +const nodeFetch = require('node-fetch'); + +nodeFetch.default = fetchMock; describe('loadConfig', () => { beforeEach(() => { process.env.MY_SECRET = 'is-secret'; process.env.SUBSTITUTE_ME = 'substituted'; + fetchMock.mock( + { + url: 'https://some.domain.io/app-config.yaml', + method: 'GET', + }, + { + body: `app: + title: Remote Example App + sessionKey: 'abc123' + escaped: \$\${Escaped} + `, + headers: { ETag: uuidv4().toString() }, + }, + ); + mockFs({ '/root/app-config.yaml': ` app: @@ -66,6 +87,7 @@ describe('loadConfig', () => { }); afterEach(() => { + fetchMock.restore(); mockFs.restore(); }); @@ -73,7 +95,7 @@ describe('loadConfig', () => { await expect( loadConfig({ configRoot: '/root', - configPaths: [], + configTargets: [], env: 'production', }), ).resolves.toEqual([ @@ -90,11 +112,37 @@ describe('loadConfig', () => { ]); }); + it('load config from remote path', async () => { + const configUrl = 'https://some.domain.io/app-config.yaml'; + + await expect( + loadConfig({ + configRoot: '/root', + configTargets: [{ url: configUrl }], + env: 'production', + remote: { + reloadIntervalSeconds: 30, + }, + }), + ).resolves.toEqual([ + { + context: configUrl, + data: { + app: { + title: 'Remote Example App', + sessionKey: 'abc123', + escaped: '${Escaped}', + }, + }, + }, + ]); + }); + it('loads config with secrets', async () => { await expect( loadConfig({ configRoot: '/root', - configPaths: ['/root/app-config.yaml'], + configTargets: [{ path: '/root/app-config.yaml' }], env: 'production', }), ).resolves.toEqual([ @@ -115,9 +163,9 @@ describe('loadConfig', () => { await expect( loadConfig({ configRoot: '/root', - configPaths: [ - '/root/app-config.yaml', - '/root/app-config.development.yaml', + configTargets: [ + { path: '/root/app-config.yaml' }, + { path: '/root/app-config.development.yaml' }, ], env: 'development', }), @@ -155,7 +203,7 @@ describe('loadConfig', () => { await expect( loadConfig({ configRoot: '/root', - configPaths: ['/root/app-config.substitute.yaml'], + configTargets: [{ path: '/root/app-config.substitute.yaml' }], env: 'development', }), ).resolves.toEqual([ @@ -180,7 +228,7 @@ describe('loadConfig', () => { await expect( loadConfig({ configRoot: '/root', - configPaths: [], + configTargets: [], watch: { onChange: onChange.resolve, stopSignal: stopSignal.promise, @@ -218,12 +266,73 @@ describe('loadConfig', () => { stopSignal.resolve(); }); + it('watches remote config urls', async () => { + const onChange = defer(); + const stopSignal = defer(); + + const configUrl = 'https://some.domain.io/app-config.yaml'; + await expect( + loadConfig({ + configRoot: '/root', + configTargets: [{ url: configUrl }], + watch: { + onChange: onChange.resolve, + stopSignal: stopSignal.promise, + }, + remote: { + reloadIntervalSeconds: 1, + }, + }), + ).resolves.toEqual([ + { + context: configUrl, + data: { + app: { + title: 'Remote Example App', + sessionKey: 'abc123', + escaped: '${Escaped}', + }, + }, + }, + ]); + + fetchMock.mock( + { + url: 'https://some.domain.io/app-config.yaml', + }, + { + body: `app: + title: NEW ReMOTe ExaMPLe App + sessionKey: 'abc123' + escaped: \$\${Escaped} + `, + headers: { ETag: uuidv4().toString() }, + }, + { overwriteRoutes: true }, + ); + + await expect(onChange.promise).resolves.toEqual([ + { + context: configUrl, + data: { + app: { + title: 'NEW ReMOTe ExaMPLe App', + sessionKey: 'abc123', + escaped: '${Escaped}', + }, + }, + }, + ]); + + stopSignal.resolve(); + }); + it('stops watching config files', async () => { const stopSignal = defer(); await loadConfig({ configRoot: '/root', - configPaths: [], + configTargets: [], watch: { onChange: () => { expect('not').toBe('called'); diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index 6803d61786..2c61b4d1ce 100644 --- a/packages/config-loader/src/loader.ts +++ b/packages/config-loader/src/loader.ts @@ -17,15 +17,66 @@ import fs from 'fs-extra'; import yaml from 'yaml'; import chokidar from 'chokidar'; -import { resolve as resolvePath, dirname, isAbsolute, basename } from 'path'; +import { basename, dirname, isAbsolute, resolve as resolvePath } from 'path'; import { AppConfig } from '@backstage/config'; import { ForwardedError } from '@backstage/errors'; import { applyConfigTransforms, - readEnvConfig, createIncludeTransform, createSubstitutionTransform, + EnvFunc, + readEnvConfig, } from './lib'; +import fetch from 'node-fetch'; +import { isValidUrl } from '@backstage/integration'; + +export type ConfigTarget = { path: string } | { url: string }; + +export type Watch = { + /** + * A listener that is called when a config file is changed. + */ + onChange: (configs: AppConfig[]) => void; + + /** + * An optional signal that stops the watcher once the promise resolves. + */ + stopSignal?: Promise; +}; + +/** + * Options that control the loading of configuration files in the backend. + * + * @public + */ +export type Remote = { + /** + * An optional remote config reloading period, in seconds + */ + reloadIntervalSeconds: number; +}; + +export type RemoteConfigProp = { + /** + * URL of the remote config + */ + url: string; + + /** + * Contents of the remote config + */ + content: string | null; + + /** + * An optional new ETag header value. Used when checking for updated config. + */ + newETag?: string; + + /** + * An optional old ETag header value. Used when checking for updated config + */ + oldETag?: string; +}; /** * Options that control the loading of configuration files in the backend. @@ -36,8 +87,8 @@ export type LoadConfigOptions = { // The root directory of the config loading context. Used to find default configs. configRoot: string; - // Absolute paths to load config files from. Configs from earlier paths have lower priority. - configPaths: string[]; + // Paths to load config files from. Configs from earlier paths have lower priority. + configTargets: ConfigTarget[]; /** @deprecated This option has been removed */ env?: string; @@ -49,22 +100,19 @@ export type LoadConfigOptions = { */ experimentalEnvFunc?: (name: string) => Promise; + /** + * An optional remote config + */ + remote?: Remote; + /** * An optional configuration that enables watching of config files. */ - watch?: { - /** - * A listener that is called when a config file is changed. - */ - onChange: (configs: AppConfig[]) => void; - - /** - * An optional signal that stops the watcher once the promise resolves. - */ - stopSignal?: Promise; - }; + watch?: Watch; }; +const HTTP_RESPONSE_HEADER_ETAG = 'ETag'; + /** * Load configuration data. * @@ -73,12 +121,30 @@ export type LoadConfigOptions = { export async function loadConfig( options: LoadConfigOptions, ): Promise { - const { configRoot, experimentalEnvFunc: envFunc, watch } = options; - const configPaths = options.configPaths.slice(); + const { configRoot, experimentalEnvFunc: envFunc, watch, remote } = options; + + const configPaths: string[] = options.configTargets + .slice() + .filter((e): e is { path: string } => e.hasOwnProperty('path')) + .map(configTarget => configTarget.path); + + let configUrls: string[] = options.configTargets + .slice() + .filter((e): e is { url: string } => e.hasOwnProperty('url')) + .map(configTarget => configTarget.url); + + const remoteConfigProps: RemoteConfigProp[] = []; + + if (remote === undefined && configUrls.length > 0) { + console.warn( + `Remote config detected, however, this feature is turned off. Remote config will be ignored.`, + ); + configUrls = []; + } // If no paths are provided, we default to reading // `app-config.yaml` and, if it exists, `app-config.local.yaml` - if (configPaths.length === 0) { + if (configPaths.length === 0 && configUrls.length === 0) { configPaths.push(resolvePath(configRoot, 'app-config.yaml')); const localConfig = resolvePath(configRoot, 'app-config.local.yaml'); @@ -114,22 +180,79 @@ export async function loadConfig( return configs; }; - let fileConfigs; + const loadRemoteConfigFiles = async () => { + const configs: AppConfig[] = []; + + const readConfigFromUrl = async (remoteConfigProp: RemoteConfigProp) => { + const response = await fetch(remoteConfigProp.url); + if (!response.ok) { + throw new Error( + `Could not read config file at ${remoteConfigProp.url}`, + ); + } + + remoteConfigProp.oldETag = remoteConfigProp.newETag ?? undefined; + remoteConfigProp.newETag = + response.headers.get(HTTP_RESPONSE_HEADER_ETAG) ?? undefined; + remoteConfigProp.content = await response.text(); + + return remoteConfigProp; + }; + + for (let i = 0; i < configUrls.length; i++) { + const remoteConfigProp = await readConfigFromUrl({ + url: configUrls[i], + content: null, + }); + + if (!isValidUrl(remoteConfigProp.url)) { + throw new Error( + `Config load path is not valid: '${remoteConfigProp.url}'`, + ); + } + + const dir = configRoot; + if (!remoteConfigProp.content) { + throw new Error(`Config is not valid`); + } + const input = yaml.parse(remoteConfigProp.content); + const substitutionTransform = createSubstitutionTransform(env); + const data = await applyConfigTransforms(dir, input, [ + substitutionTransform, + ]); + + configs.push({ data, context: remoteConfigProp.url }); + + remoteConfigProps.push(remoteConfigProp); + } + + return configs; + }; + + let fileConfigs: AppConfig[]; try { fileConfigs = await loadConfigFiles(); } catch (error) { throw new ForwardedError('Failed to read static configuration file', error); } + let remoteConfigs: AppConfig[] = []; + if (remote) { + try { + remoteConfigs = await loadRemoteConfigFiles(); + } catch (error) { + throw new Error(`Failed to read remote configuration file, ${error}`); + } + } + const envConfigs = await readEnvConfig(process.env); - // Set up config file watching if requested by the caller - if (watch) { - let currentSerializedConfig = JSON.stringify(fileConfigs); - + const watchConfigFile = (watchProp: Watch) => { const watcher = chokidar.watch(configPaths, { usePolling: process.env.NODE_ENV === 'test', }); + + let currentSerializedConfig = JSON.stringify(fileConfigs); watcher.on('change', async () => { try { const newConfigs = await loadConfigFiles(); @@ -140,18 +263,77 @@ export async function loadConfig( } currentSerializedConfig = newSerializedConfig; - watch.onChange([...newConfigs, ...envConfigs]); + watchProp.onChange([...remoteConfigs, ...newConfigs, ...envConfigs]); } catch (error) { console.error(`Failed to reload configuration files, ${error}`); } }); - if (watch.stopSignal) { - watch.stopSignal.then(() => { + if (watchProp.stopSignal) { + watchProp.stopSignal.then(() => { watcher.close(); }); } + }; + + const watchRemoteConfig = (watchProp: Watch, remoteProp: Remote) => { + const hasConfigChanged = async (remoteConfigProp: RemoteConfigProp) => { + const requestProps = { method: 'HEAD' }; + const { headers } = await fetch(remoteConfigProp.url, requestProps); + remoteConfigProp.oldETag = remoteConfigProp.newETag ?? undefined; + remoteConfigProp.newETag = + headers.get(HTTP_RESPONSE_HEADER_ETAG) ?? undefined; + + return ( + remoteConfigProp.oldETag !== undefined && + remoteConfigProp.newETag !== undefined && + remoteConfigProp.oldETag !== remoteConfigProp.newETag + ); + }; + + let handle: NodeJS.Timeout | undefined; + try { + handle = setInterval(async () => { + console.info(`Checking for config update`); + for (const remoteConfigProp of remoteConfigProps) { + if (await hasConfigChanged(remoteConfigProp)) { + console.info(`Remote config change, reloading config ...`); + const newRemoteConfigs = await loadRemoteConfigFiles(); + watchProp.onChange([ + ...newRemoteConfigs, + ...fileConfigs, + ...envConfigs, + ]); + console.info(`Remote config reloaded`); + break; + } + } + }, remoteProp.reloadIntervalSeconds * 1000); + } catch (error) { + console.error(`Failed to reload configuration files, ${error}`); + } + + if (watchProp.stopSignal) { + watchProp.stopSignal.then(() => { + if (handle !== undefined) { + console.info(`Stopping remote config watch`); + clearInterval(handle); + handle = undefined; + } + }); + } + }; + + // Set up config file watching if requested by the caller + if (watch) { + watchConfigFile(watch); } - return [...fileConfigs, ...envConfigs]; + if (watch && remote) { + watchRemoteConfig(watch, remote); + } + + return remote + ? [...remoteConfigs, ...fileConfigs, ...envConfigs] + : [...fileConfigs, ...envConfigs]; } diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index 01f84383fe..e07f61eea6 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -347,6 +347,11 @@ export type GoogleGcsIntegrationConfig = { privateKey?: string; }; +// Warning: (ae-missing-release-tag) "isValidUrl" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export function isValidUrl(url: string): boolean; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (ae-missing-release-tag) "readAwsS3IntegrationConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/packages/integration/src/index.ts b/packages/integration/src/index.ts index 3d5f336e8d..b8992f52d2 100644 --- a/packages/integration/src/index.ts +++ b/packages/integration/src/index.ts @@ -27,6 +27,7 @@ export * from './gitlab'; export * from './googleGcs'; export * from './awsS3'; export { defaultScmResolveUrl } from './helpers'; +export { isValidUrl } from './helpers'; export { ScmIntegrations } from './ScmIntegrations'; export type { ScmIntegration, ScmIntegrationsGroup } from './types'; export type { ScmIntegrationRegistry } from './registry'; diff --git a/yarn.lock b/yarn.lock index c905fbea6f..e45d685bf8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11555,6 +11555,11 @@ core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.10: resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== +core-js@^3.0.0: + version "3.18.3" + resolved "https://registry.npmjs.org/core-js/-/core-js-3.18.3.tgz#86a0bba2d8ec3df860fefcc07a8d119779f01509" + integrity sha512-tReEhtMReZaPFVw7dajMx0vlsz3oOb8ajgPoHVYGxr8ErnZ6PcYEvvmjGmXlfpnxpkYSdOQttjB+MvVbCGfvLw== + core-js@^3.0.4, core-js@^3.6.5, core-js@^3.8.2: version "3.15.0" resolved "https://registry.npmjs.org/core-js/-/core-js-3.15.0.tgz#db9554ebce0b6fd90dc9b1f2465c841d2d055044" @@ -14238,6 +14243,34 @@ fecha@^4.2.0: resolved "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz#3ffb6395453e3f3efff850404f0a59b6747f5f41" integrity sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg== +fetch-blob@2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/fetch-blob/-/fetch-blob-2.1.2.tgz#a7805db1361bd44c1ef62bb57fb5fe8ea173ef3c" + integrity sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow== + +fetch-mock-jest@1.5.1: + version "1.5.1" + resolved "https://registry.npmjs.org/fetch-mock-jest/-/fetch-mock-jest-1.5.1.tgz#0e13df990d286d9239e284f12b279ed509bf53cd" + integrity sha512-+utwzP8C+Pax1GSka3nFXILWMY3Er2L+s090FOgqVNrNCPp0fDqgXnAHAJf12PLHi0z4PhcTaZNTz8e7K3fjqQ== + dependencies: + fetch-mock "^9.11.0" + +fetch-mock@^9.11.0: + version "9.11.0" + resolved "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.11.0.tgz#371c6fb7d45584d2ae4a18ee6824e7ad4b637a3f" + integrity sha512-PG1XUv+x7iag5p/iNHD4/jdpxL9FtVSqRMUQhPab4hVDt80T1MH5ehzVrL2IdXO9Q2iBggArFvPqjUbHFuI58Q== + dependencies: + "@babel/core" "^7.0.0" + "@babel/runtime" "^7.0.0" + core-js "^3.0.0" + debug "^4.1.1" + glob-to-regexp "^0.4.0" + is-subset "^0.1.1" + lodash.isequal "^4.5.0" + path-to-regexp "^2.2.1" + querystring "^0.2.0" + whatwg-url "^6.5.0" + fetch-readablestream@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/fetch-readablestream/-/fetch-readablestream-0.2.0.tgz#eaa6d1a76b12de2d4731a343393c6ccdcfe2c795" @@ -15062,7 +15095,7 @@ glob-to-regexp@^0.3.0: resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= -glob-to-regexp@^0.4.1: +glob-to-regexp@^0.4.0, glob-to-regexp@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== @@ -17103,6 +17136,11 @@ is-subdir@^1.1.1: dependencies: better-path-resolve "1.0.0" +is-subset@^0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" + integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY= + is-svg@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" @@ -18971,7 +19009,7 @@ lodash.isempty@^4.4.0: resolved "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" integrity sha1-b4bL7di+TsmHvpqvM8loTbGzHn4= -lodash.isequal@^4.0.0: +lodash.isequal@^4.0.0, lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= @@ -20815,6 +20853,13 @@ node-fetch@2.6.1, node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1: resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-fetch@2.6.5: + version "2.6.5" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz#42735537d7f080a7e5f78b6c549b7146be1742fd" + integrity sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ== + dependencies: + whatwg-url "^5.0.0" + node-forge@^0.10.0: version "0.10.0" resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" @@ -22153,6 +22198,11 @@ path-to-regexp@^1.7.0: dependencies: isarray "0.0.1" +path-to-regexp@^2.2.1: + version "2.4.0" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704" + integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w== + path-type@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -27219,6 +27269,11 @@ tr46@^2.0.2: dependencies: punycode "^2.1.1" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + "traverse@>=0.3.0 <0.4": version "0.3.9" resolved "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" @@ -28449,6 +28504,11 @@ web-streams-polyfill@4.0.0-beta.1: resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.1.tgz#3b19b9817374b7cee06d374ba7eeb3aeb80e8c95" integrity sha512-3ux37gEX670UUphBF9AMCq8XM6iQ8Ac6A+DSRRjDoRBm1ufCkaCDdNVbaqq60PsEkdNlLKrGtv/YBP4EJXqNtQ== +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" @@ -28671,7 +28731,15 @@ whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== -whatwg-url@^6.4.1: +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +whatwg-url@^6.4.1, whatwg-url@^6.5.0: version "6.5.0" resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ== From e51740f9c96cb51c3e742df3289b56ce92264925 Mon Sep 17 00:00:00 2001 From: Matto Date: Tue, 19 Oct 2021 23:18:19 +1100 Subject: [PATCH 20/65] Added documentation Signed-off-by: Matto --- docs/conf/writing.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/conf/writing.md b/docs/conf/writing.md index 4288d23b17..86b844484b 100644 --- a/docs/conf/writing.md +++ b/docs/conf/writing.md @@ -1,7 +1,6 @@ --- -id: writing -title: Writing Backstage Configuration Files -description: Documentation on Writing Backstage Configuration Files +id: writing title: Writing Backstage Configuration Files description: Documentation on Writing +Backstage Configuration Files --- ## File Format @@ -67,13 +66,15 @@ production build. ## Configuration Files -It is possible to have multiple configuration files, both to support different -environments, but also to define configuration that is local to specific -packages. The configuration files to load are selected using a `--config ` -flag, and it is possible to load any number of files. Paths are relative to the -working directory of the executed process, for example `package/backend`. This -means that to select a config file in the repo root when running the backend, -you would use `--config ../../my-config.yaml`. +It is possible to have multiple configuration files (bundled and/or remote), +both to support different environments, but also to define configuration that is +local to specific packages. The configuration files to load are selected using a +`--config ` flag, and it is possible to load any number of +files. Paths are relative to the working directory of the executed process, for +example `package/backend`. This means that to select a config file in the repo +root when running the backend, you would use `--config ../../my-config.yaml`, +and for config file on a config server you would use +`--config https://some.domain.io/app-config.yaml` If no `config` flags are specified, the default behavior is to load `app-config.yaml` and, if it exists, `app-config.local.yaml` from the repo root. @@ -85,7 +86,7 @@ are NOT loaded. To include them you need to explicitly include them with a flag, for example: ```shell -yarn start --config ../../app-config.yaml --config ../../app-config.staging.yaml +yarn start --config ../../app-config.yaml --config ../../app-config.staging.yaml --config https://some.domain.io/app-config.yaml ``` All loaded configuration files are merged together using the following rules: From ce843364f09c59a32f3d4ba8c178d33e73c51d9b Mon Sep 17 00:00:00 2001 From: Matto Date: Thu, 21 Oct 2021 18:20:36 +1100 Subject: [PATCH 21/65] Remove the dependency on Etag headers for reloading config Signed-off-by: Matto --- docs/conf/writing.md | 5 +- packages/cli/package.json | 2 - packages/config-loader/package.json | 4 +- packages/config-loader/src/loader.test.ts | 79 ++++++++++++---------- packages/config-loader/src/loader.ts | 82 +++++++---------------- yarn.lock | 7 -- 6 files changed, 73 insertions(+), 106 deletions(-) diff --git a/docs/conf/writing.md b/docs/conf/writing.md index 86b844484b..a0e29820ed 100644 --- a/docs/conf/writing.md +++ b/docs/conf/writing.md @@ -1,6 +1,7 @@ --- -id: writing title: Writing Backstage Configuration Files description: Documentation on Writing -Backstage Configuration Files +id: writing +title: Writing Backstage Configuration Files +description: Documentation on Writing Backstage Configuration Files --- ## File Format diff --git a/packages/cli/package.json b/packages/cli/package.json index d85adff199..b9ba9fe8a3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -31,14 +31,12 @@ "@backstage/cli-common": "^0.1.4", "@babel/core": "^7.4.4", "@babel/plugin-transform-modules-commonjs": "^7.4.4", - "@backstage/cli-common": "^0.1.3", "@backstage/integration": "^0.6.5", "@backstage/config": "^0.1.10", "@backstage/config-loader": "^0.7.0", "@backstage/errors": "^0.1.3", "@backstage/types": "^0.1.1", "@backstage/config-loader": "^0.6.8", - "@backstage/backend-common": "^0.9.4", "@hot-loader/react-dom": "^16.13.0", "@lerna/package-graph": "^4.0.0", "@lerna/project": "^4.0.0", diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index cab3a43fc5..456bca90ad 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -44,7 +44,6 @@ "json-schema-merge-allof": "^0.8.1", "json-schema-traverse": "^1.0.0", "typescript-json-schema": "^0.50.1", - "uuid": "^8.3.2", "yaml": "^1.9.2", "yup": "^0.32.9", "node-fetch": "2.6.5" @@ -56,8 +55,7 @@ "@types/node": "^14.14.32", "@types/yup": "^0.29.13", "mock-fs": "^5.1.0", - "fetch-mock-jest": "1.5.1", - "fetch-mock": "^9.11.0" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/packages/config-loader/src/loader.test.ts b/packages/config-loader/src/loader.test.ts index 190904a870..e8b5a8828b 100644 --- a/packages/config-loader/src/loader.test.ts +++ b/packages/config-loader/src/loader.test.ts @@ -18,33 +18,47 @@ import { AppConfig } from '@backstage/config'; import { loadConfig } from './loader'; import mockFs from 'mock-fs'; import fs from 'fs-extra'; -import { v4 as uuidv4 } from 'uuid'; - -const fetchMock = require('fetch-mock').sandbox(); -const nodeFetch = require('node-fetch'); - -nodeFetch.default = fetchMock; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; describe('loadConfig', () => { + const server = setupServer(); + const initialLoaderHandler = rest.get( + `https://some.domain.io/app-config.yaml`, + (_req, res, ctx) => { + return res( + ctx.body( + `app: + title: Remote Example App + sessionKey: 'abc123' + escaped: \$\${Escaped} + `, + ), + ); + }, + ); + + const reloadHandler = rest.get( + `https://some.domain.io/app-config.yaml`, + (_req, res, ctx) => { + return res( + ctx.body( + `app: + title: NEW ReMOTe ExaMPLe App + sessionKey: 'abc123' + escaped: \$\${Escaped} + `, + ), + ); + }, + ); + + beforeAll(() => server.listen()); + beforeEach(() => { process.env.MY_SECRET = 'is-secret'; process.env.SUBSTITUTE_ME = 'substituted'; - fetchMock.mock( - { - url: 'https://some.domain.io/app-config.yaml', - method: 'GET', - }, - { - body: `app: - title: Remote Example App - sessionKey: 'abc123' - escaped: \$\${Escaped} - `, - headers: { ETag: uuidv4().toString() }, - }, - ); - mockFs({ '/root/app-config.yaml': ` app: @@ -87,10 +101,12 @@ describe('loadConfig', () => { }); afterEach(() => { - fetchMock.restore(); mockFs.restore(); + server.resetHandlers(); }); + afterAll(() => server.close()); + it('load config from default path', async () => { await expect( loadConfig({ @@ -113,6 +129,8 @@ describe('loadConfig', () => { }); it('load config from remote path', async () => { + server.use(initialLoaderHandler); + const configUrl = 'https://some.domain.io/app-config.yaml'; await expect( @@ -267,6 +285,8 @@ describe('loadConfig', () => { }); it('watches remote config urls', async () => { + server.use(initialLoaderHandler); + const onChange = defer(); const stopSignal = defer(); @@ -296,20 +316,7 @@ describe('loadConfig', () => { }, ]); - fetchMock.mock( - { - url: 'https://some.domain.io/app-config.yaml', - }, - { - body: `app: - title: NEW ReMOTe ExaMPLe App - sessionKey: 'abc123' - escaped: \$\${Escaped} - `, - headers: { ETag: uuidv4().toString() }, - }, - { overwriteRoutes: true }, - ); + server.use(reloadHandler); await expect(onChange.promise).resolves.toEqual([ { diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index 2c61b4d1ce..c35b742f92 100644 --- a/packages/config-loader/src/loader.ts +++ b/packages/config-loader/src/loader.ts @@ -56,6 +56,7 @@ export type Remote = { reloadIntervalSeconds: number; }; +/** @public */ export type RemoteConfigProp = { /** * URL of the remote config @@ -111,8 +112,6 @@ export type LoadConfigOptions = { watch?: Watch; }; -const HTTP_RESPONSE_HEADER_ETAG = 'ETag'; - /** * Load configuration data. * @@ -128,18 +127,15 @@ export async function loadConfig( .filter((e): e is { path: string } => e.hasOwnProperty('path')) .map(configTarget => configTarget.path); - let configUrls: string[] = options.configTargets + const configUrls: string[] = options.configTargets .slice() .filter((e): e is { url: string } => e.hasOwnProperty('url')) .map(configTarget => configTarget.url); - const remoteConfigProps: RemoteConfigProp[] = []; - if (remote === undefined && configUrls.length > 0) { - console.warn( + throw new Error( `Remote config detected, however, this feature is turned off. Remote config will be ignored.`, ); - configUrls = []; } // If no paths are provided, we default to reading @@ -183,47 +179,32 @@ export async function loadConfig( const loadRemoteConfigFiles = async () => { const configs: AppConfig[] = []; - const readConfigFromUrl = async (remoteConfigProp: RemoteConfigProp) => { - const response = await fetch(remoteConfigProp.url); + const readConfigFromUrl = async (url: string) => { + const response = await fetch(url); if (!response.ok) { - throw new Error( - `Could not read config file at ${remoteConfigProp.url}`, - ); + throw new Error(`Could not read config file at ${url}`); } - remoteConfigProp.oldETag = remoteConfigProp.newETag ?? undefined; - remoteConfigProp.newETag = - response.headers.get(HTTP_RESPONSE_HEADER_ETAG) ?? undefined; - remoteConfigProp.content = await response.text(); - - return remoteConfigProp; + return await response.text(); }; for (let i = 0; i < configUrls.length; i++) { - const remoteConfigProp = await readConfigFromUrl({ - url: configUrls[i], - content: null, - }); - - if (!isValidUrl(remoteConfigProp.url)) { - throw new Error( - `Config load path is not valid: '${remoteConfigProp.url}'`, - ); + const configUrl = configUrls[i]; + if (!isValidUrl(configUrl)) { + throw new Error(`Config load path is not valid: '${configUrl}'`); } - const dir = configRoot; - if (!remoteConfigProp.content) { + const remoteConfigContent = await readConfigFromUrl(configUrl); + if (!remoteConfigContent) { throw new Error(`Config is not valid`); } - const input = yaml.parse(remoteConfigProp.content); + const configYaml = yaml.parse(remoteConfigContent); const substitutionTransform = createSubstitutionTransform(env); - const data = await applyConfigTransforms(dir, input, [ + const data = await applyConfigTransforms(configRoot, configYaml, [ substitutionTransform, ]); - configs.push({ data, context: remoteConfigProp.url }); - - remoteConfigProps.push(remoteConfigProp); + configs.push({ data, context: configUrl }); } return configs; @@ -277,17 +258,12 @@ export async function loadConfig( }; const watchRemoteConfig = (watchProp: Watch, remoteProp: Remote) => { - const hasConfigChanged = async (remoteConfigProp: RemoteConfigProp) => { - const requestProps = { method: 'HEAD' }; - const { headers } = await fetch(remoteConfigProp.url, requestProps); - remoteConfigProp.oldETag = remoteConfigProp.newETag ?? undefined; - remoteConfigProp.newETag = - headers.get(HTTP_RESPONSE_HEADER_ETAG) ?? undefined; - + const hasConfigChanged = async ( + oldRemoteConfigs: AppConfig[], + newRemoteConfigs: AppConfig[], + ) => { return ( - remoteConfigProp.oldETag !== undefined && - remoteConfigProp.newETag !== undefined && - remoteConfigProp.oldETag !== remoteConfigProp.newETag + JSON.stringify(oldRemoteConfigs) !== JSON.stringify(newRemoteConfigs) ); }; @@ -295,18 +271,12 @@ export async function loadConfig( try { handle = setInterval(async () => { console.info(`Checking for config update`); - for (const remoteConfigProp of remoteConfigProps) { - if (await hasConfigChanged(remoteConfigProp)) { - console.info(`Remote config change, reloading config ...`); - const newRemoteConfigs = await loadRemoteConfigFiles(); - watchProp.onChange([ - ...newRemoteConfigs, - ...fileConfigs, - ...envConfigs, - ]); - console.info(`Remote config reloaded`); - break; - } + const newRemoteConfigs = await loadRemoteConfigFiles(); + if (await hasConfigChanged(remoteConfigs, newRemoteConfigs)) { + remoteConfigs = newRemoteConfigs; + console.info(`Remote config change, reloading config ...`); + watchProp.onChange([...remoteConfigs, ...fileConfigs, ...envConfigs]); + console.info(`Remote config reloaded`); } }, remoteProp.reloadIntervalSeconds * 1000); } catch (error) { diff --git a/yarn.lock b/yarn.lock index e45d685bf8..113b0d48c2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14248,13 +14248,6 @@ fetch-blob@2.1.2: resolved "https://registry.npmjs.org/fetch-blob/-/fetch-blob-2.1.2.tgz#a7805db1361bd44c1ef62bb57fb5fe8ea173ef3c" integrity sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow== -fetch-mock-jest@1.5.1: - version "1.5.1" - resolved "https://registry.npmjs.org/fetch-mock-jest/-/fetch-mock-jest-1.5.1.tgz#0e13df990d286d9239e284f12b279ed509bf53cd" - integrity sha512-+utwzP8C+Pax1GSka3nFXILWMY3Er2L+s090FOgqVNrNCPp0fDqgXnAHAJf12PLHi0z4PhcTaZNTz8e7K3fjqQ== - dependencies: - fetch-mock "^9.11.0" - fetch-mock@^9.11.0: version "9.11.0" resolved "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.11.0.tgz#371c6fb7d45584d2ae4a18ee6824e7ad4b637a3f" From 8e85a0bca056f8ea245405a7555911e2a001ca66 Mon Sep 17 00:00:00 2001 From: Matto Date: Tue, 26 Oct 2021 21:50:49 +1100 Subject: [PATCH 22/65] Change variable name Signed-off-by: Matto --- packages/config-loader/api-report.md | 12 ++++++------ packages/config-loader/src/index.ts | 7 ++++++- packages/config-loader/src/loader.ts | 22 ++++++++++------------ 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/packages/config-loader/api-report.md b/packages/config-loader/api-report.md index 4ed409e3ce..3b61b67d7b 100644 --- a/packages/config-loader/api-report.md +++ b/packages/config-loader/api-report.md @@ -46,8 +46,8 @@ export type LoadConfigOptions = { configTargets: ConfigTarget[]; env?: string; experimentalEnvFunc?: (name: string) => Promise; - remote?: Remote; - watch?: Watch; + remote?: LoadConfigOptionsRemote; + watch?: LoadConfigOptionsWatch; }; // @public @@ -73,10 +73,10 @@ export function readEnvConfig(env: { [name: string]: string | undefined; }): AppConfig[]; -// Warning: (ae-missing-release-tag) "Remote" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "LoadConfigOptionsRemote" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type Remote = { +export type LoadConfigOptionsRemote = { reloadIntervalSeconds: number; }; @@ -88,10 +88,10 @@ export type TransformFunc = ( }, ) => T | undefined; -// Warning: (ae-missing-release-tag) "Watch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "LoadConfigOptionsWatch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type Watch = { +export type LoadConfigOptionsWatch = { onChange: (configs: AppConfig[]) => void; stopSignal?: Promise; }; diff --git a/packages/config-loader/src/index.ts b/packages/config-loader/src/index.ts index ee3e6413ad..97f0d301a3 100644 --- a/packages/config-loader/src/index.ts +++ b/packages/config-loader/src/index.ts @@ -29,4 +29,9 @@ export type { TransformFunc, } from './lib'; export { loadConfig } from './loader'; -export type { ConfigTarget, LoadConfigOptions, Watch, Remote } from './loader'; +export type { + ConfigTarget, + LoadConfigOptions, + LoadConfigOptionsWatch, + LoadConfigOptionsRemote, +} from './loader'; diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index c35b742f92..8c4bbae9d6 100644 --- a/packages/config-loader/src/loader.ts +++ b/packages/config-loader/src/loader.ts @@ -32,7 +32,7 @@ import { isValidUrl } from '@backstage/integration'; export type ConfigTarget = { path: string } | { url: string }; -export type Watch = { +export type LoadConfigOptionsWatch = { /** * A listener that is called when a config file is changed. */ @@ -44,12 +44,7 @@ export type Watch = { stopSignal?: Promise; }; -/** - * Options that control the loading of configuration files in the backend. - * - * @public - */ -export type Remote = { +export type LoadConfigOptionsRemote = { /** * An optional remote config reloading period, in seconds */ @@ -104,12 +99,12 @@ export type LoadConfigOptions = { /** * An optional remote config */ - remote?: Remote; + remote?: LoadConfigOptionsRemote; /** * An optional configuration that enables watching of config files. */ - watch?: Watch; + watch?: LoadConfigOptionsWatch; }; /** @@ -222,13 +217,13 @@ export async function loadConfig( try { remoteConfigs = await loadRemoteConfigFiles(); } catch (error) { - throw new Error(`Failed to read remote configuration file, ${error}`); + throw new ForwardedError(`Failed to read remote configuration file, ${error}`); } } const envConfigs = await readEnvConfig(process.env); - const watchConfigFile = (watchProp: Watch) => { + const watchConfigFile = (watchProp: LoadConfigOptionsWatch) => { const watcher = chokidar.watch(configPaths, { usePolling: process.env.NODE_ENV === 'test', }); @@ -257,7 +252,10 @@ export async function loadConfig( } }; - const watchRemoteConfig = (watchProp: Watch, remoteProp: Remote) => { + const watchRemoteConfig = ( + watchProp: LoadConfigOptionsWatch, + remoteProp: LoadConfigOptionsRemote, + ) => { const hasConfigChanged = async ( oldRemoteConfigs: AppConfig[], newRemoteConfigs: AppConfig[], From 1cecd737f98cc0fca824073af9e0edd9cd75410d Mon Sep 17 00:00:00 2001 From: Matto Date: Wed, 27 Oct 2021 20:33:33 +1100 Subject: [PATCH 23/65] Removed dependency, introduced isValidUrl, and reinstated `configPaths` Signed-off-by: Matto --- packages/backend-common/src/config.ts | 3 +- packages/backend-common/src/urls.test.ts | 34 ++++++++++++++ packages/backend-common/src/urls.ts | 25 +++++++++++ packages/cli/src/lib/config.ts | 3 +- packages/cli/src/lib/urls.test.ts | 34 ++++++++++++++ packages/cli/src/lib/urls.ts | 25 +++++++++++ packages/config-loader/api-report.md | 1 + packages/config-loader/package.json | 1 - packages/config-loader/src/lib/index.ts | 1 + packages/config-loader/src/lib/urls.test.ts | 34 ++++++++++++++ packages/config-loader/src/lib/urls.ts | 25 +++++++++++ packages/config-loader/src/loader.test.ts | 49 ++++++++++++++++++++- packages/config-loader/src/loader.ts | 14 +++++- yarn.lock | 37 ++-------------- 14 files changed, 247 insertions(+), 39 deletions(-) create mode 100644 packages/backend-common/src/urls.test.ts create mode 100644 packages/backend-common/src/urls.ts create mode 100644 packages/cli/src/lib/urls.test.ts create mode 100644 packages/cli/src/lib/urls.ts create mode 100644 packages/config-loader/src/lib/urls.test.ts create mode 100644 packages/config-loader/src/lib/urls.ts diff --git a/packages/backend-common/src/config.ts b/packages/backend-common/src/config.ts index 90131ebbe7..9b0eb8dcd0 100644 --- a/packages/backend-common/src/config.ts +++ b/packages/backend-common/src/config.ts @@ -21,7 +21,7 @@ import { findPaths } from '@backstage/cli-common'; import { Config, ConfigReader } from '@backstage/config'; import { JsonValue } from '@backstage/types'; import { ConfigTarget, loadConfig } from '@backstage/config-loader'; -import { isValidUrl } from '@backstage/integration'; +import { isValidUrl } from './urls'; export class ObservableConfigProxy implements Config { private config: Config = new ConfigReader({}); @@ -162,6 +162,7 @@ export async function loadBackendConfig(options: { const configs = await loadConfig({ configRoot: paths.targetRoot, + configPaths: [], configTargets: configTargets, watch: { onChange(newConfigs) { diff --git a/packages/backend-common/src/urls.test.ts b/packages/backend-common/src/urls.test.ts new file mode 100644 index 0000000000..c2a67fb849 --- /dev/null +++ b/packages/backend-common/src/urls.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2021 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 { isValidUrl } from './urls'; + +describe('isValidUrl', () => { + it('should return true for url', () => { + const validUrl = isValidUrl('http://some.valid.url'); + expect(validUrl).toBe(true); + }); + + it('should return false for absolute path', () => { + const validUrl = isValidUrl('/some/absolute/path'); + expect(validUrl).toBe(false); + }); + + it('should return false for relative path', () => { + const validUrl = isValidUrl('../some/relative/path'); + expect(validUrl).toBe(false); + }); +}); diff --git a/packages/backend-common/src/urls.ts b/packages/backend-common/src/urls.ts new file mode 100644 index 0000000000..848cea25d9 --- /dev/null +++ b/packages/backend-common/src/urls.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2021 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 function isValidUrl(url: string): boolean { + try { + // eslint-disable-next-line no-new + new URL(url); + return true; + } catch { + return false; + } +} diff --git a/packages/cli/src/lib/config.ts b/packages/cli/src/lib/config.ts index 79d30ee220..80b374039f 100644 --- a/packages/cli/src/lib/config.ts +++ b/packages/cli/src/lib/config.ts @@ -21,7 +21,7 @@ import { } from '@backstage/config-loader'; import { ConfigReader } from '@backstage/config'; import { paths } from './paths'; -import { isValidUrl } from '@backstage/integration'; +import { isValidUrl } from './urls'; type Options = { args: string[]; @@ -58,6 +58,7 @@ export async function loadCliConfig(options: Options) { ? async name => process.env[name] || 'x' : undefined, configRoot: paths.targetRoot, + configPaths: [], configTargets: configTargets, }); diff --git a/packages/cli/src/lib/urls.test.ts b/packages/cli/src/lib/urls.test.ts new file mode 100644 index 0000000000..c2a67fb849 --- /dev/null +++ b/packages/cli/src/lib/urls.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2021 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 { isValidUrl } from './urls'; + +describe('isValidUrl', () => { + it('should return true for url', () => { + const validUrl = isValidUrl('http://some.valid.url'); + expect(validUrl).toBe(true); + }); + + it('should return false for absolute path', () => { + const validUrl = isValidUrl('/some/absolute/path'); + expect(validUrl).toBe(false); + }); + + it('should return false for relative path', () => { + const validUrl = isValidUrl('../some/relative/path'); + expect(validUrl).toBe(false); + }); +}); diff --git a/packages/cli/src/lib/urls.ts b/packages/cli/src/lib/urls.ts new file mode 100644 index 0000000000..848cea25d9 --- /dev/null +++ b/packages/cli/src/lib/urls.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2021 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 function isValidUrl(url: string): boolean { + try { + // eslint-disable-next-line no-new + new URL(url); + return true; + } catch { + return false; + } +} diff --git a/packages/config-loader/api-report.md b/packages/config-loader/api-report.md index 3b61b67d7b..be4678aa0f 100644 --- a/packages/config-loader/api-report.md +++ b/packages/config-loader/api-report.md @@ -43,6 +43,7 @@ export function loadConfig(options: LoadConfigOptions): Promise; // @public export type LoadConfigOptions = { configRoot: string; + configPaths: string[]; configTargets: ConfigTarget[]; env?: string; experimentalEnvFunc?: (name: string) => Promise; diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 456bca90ad..58b0061996 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -30,7 +30,6 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/integration": "^0.6.5", "@backstage/cli-common": "^0.1.4", "@backstage/config": "^0.1.9", "@backstage/errors": "^0.1.3", diff --git a/packages/config-loader/src/lib/index.ts b/packages/config-loader/src/lib/index.ts index 32a0191cae..ca88b771ba 100644 --- a/packages/config-loader/src/lib/index.ts +++ b/packages/config-loader/src/lib/index.ts @@ -17,3 +17,4 @@ export { readEnvConfig } from './env'; export * from './transform'; export * from './schema'; +export { isValidUrl } from './urls'; diff --git a/packages/config-loader/src/lib/urls.test.ts b/packages/config-loader/src/lib/urls.test.ts new file mode 100644 index 0000000000..c2a67fb849 --- /dev/null +++ b/packages/config-loader/src/lib/urls.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2021 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 { isValidUrl } from './urls'; + +describe('isValidUrl', () => { + it('should return true for url', () => { + const validUrl = isValidUrl('http://some.valid.url'); + expect(validUrl).toBe(true); + }); + + it('should return false for absolute path', () => { + const validUrl = isValidUrl('/some/absolute/path'); + expect(validUrl).toBe(false); + }); + + it('should return false for relative path', () => { + const validUrl = isValidUrl('../some/relative/path'); + expect(validUrl).toBe(false); + }); +}); diff --git a/packages/config-loader/src/lib/urls.ts b/packages/config-loader/src/lib/urls.ts new file mode 100644 index 0000000000..848cea25d9 --- /dev/null +++ b/packages/config-loader/src/lib/urls.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2021 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 function isValidUrl(url: string): boolean { + try { + // eslint-disable-next-line no-new + new URL(url); + return true; + } catch { + return false; + } +} diff --git a/packages/config-loader/src/loader.test.ts b/packages/config-loader/src/loader.test.ts index e8b5a8828b..804254afa0 100644 --- a/packages/config-loader/src/loader.test.ts +++ b/packages/config-loader/src/loader.test.ts @@ -67,6 +67,13 @@ describe('loadConfig', () => { $file: secrets/session-key.txt escaped: \$\${Escaped} `, + '/root/app-config2.yaml': ` + app: + title: Example App 2 + sessionKey: + $file: secrets/session-key.txt + escaped: \$\${Escaped} + `, '/root/app-config.development.yaml': ` app: sessionKey: development-key @@ -111,6 +118,7 @@ describe('loadConfig', () => { await expect( loadConfig({ configRoot: '/root', + configPaths: [], configTargets: [], env: 'production', }), @@ -136,6 +144,7 @@ describe('loadConfig', () => { await expect( loadConfig({ configRoot: '/root', + configPaths: [], configTargets: [{ url: configUrl }], env: 'production', remote: { @@ -156,10 +165,43 @@ describe('loadConfig', () => { ]); }); - it('loads config with secrets', async () => { + it('loads config with secrets from two different files', async () => { await expect( loadConfig({ configRoot: '/root', + configPaths: ['/root/app-config2.yaml'], + configTargets: [{ path: '/root/app-config.yaml' }], + env: 'production', + }), + ).resolves.toEqual([ + { + context: 'app-config.yaml', + data: { + app: { + title: 'Example App', + sessionKey: 'abc123', + escaped: '${Escaped}', + }, + }, + }, + { + context: 'app-config2.yaml', + data: { + app: { + title: 'Example App 2', + sessionKey: 'abc123', + escaped: '${Escaped}', + }, + }, + }, + ]); + }); + + it('loads config with secrets from single file', async () => { + await expect( + loadConfig({ + configRoot: '/root', + configPaths: ['/root/app-config.yaml'], configTargets: [{ path: '/root/app-config.yaml' }], env: 'production', }), @@ -181,6 +223,7 @@ describe('loadConfig', () => { await expect( loadConfig({ configRoot: '/root', + configPaths: [], configTargets: [ { path: '/root/app-config.yaml' }, { path: '/root/app-config.development.yaml' }, @@ -221,6 +264,7 @@ describe('loadConfig', () => { await expect( loadConfig({ configRoot: '/root', + configPaths: [], configTargets: [{ path: '/root/app-config.substitute.yaml' }], env: 'development', }), @@ -246,6 +290,7 @@ describe('loadConfig', () => { await expect( loadConfig({ configRoot: '/root', + configPaths: [], configTargets: [], watch: { onChange: onChange.resolve, @@ -294,6 +339,7 @@ describe('loadConfig', () => { await expect( loadConfig({ configRoot: '/root', + configPaths: [], configTargets: [{ url: configUrl }], watch: { onChange: onChange.resolve, @@ -339,6 +385,7 @@ describe('loadConfig', () => { await loadConfig({ configRoot: '/root', + configPaths: [], configTargets: [], watch: { onChange: () => { diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index 8c4bbae9d6..338f199132 100644 --- a/packages/config-loader/src/loader.ts +++ b/packages/config-loader/src/loader.ts @@ -25,10 +25,10 @@ import { createIncludeTransform, createSubstitutionTransform, EnvFunc, + isValidUrl, readEnvConfig, } from './lib'; import fetch from 'node-fetch'; -import { isValidUrl } from '@backstage/integration'; export type ConfigTarget = { path: string } | { url: string }; @@ -83,6 +83,11 @@ export type LoadConfigOptions = { // The root directory of the config loading context. Used to find default configs. configRoot: string; + /** Absolute paths to load config files from. Configs from earlier paths have lower priority. + * @deprecated Use {@link configTargets} instead. + */ + configPaths: string[]; + // Paths to load config files from. Configs from earlier paths have lower priority. configTargets: ConfigTarget[]; @@ -122,6 +127,13 @@ export async function loadConfig( .filter((e): e is { path: string } => e.hasOwnProperty('path')) .map(configTarget => configTarget.path); + // Append deprecated configPaths to the absolute config paths received via configTargets. + options.configPaths.forEach(cp => { + if (!configPaths.includes(cp)) { + configPaths.push(cp); + } + }); + const configUrls: string[] = options.configTargets .slice() .filter((e): e is { url: string } => e.hasOwnProperty('url')) diff --git a/yarn.lock b/yarn.lock index 113b0d48c2..079cd96a5c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11555,11 +11555,6 @@ core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.10: resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== -core-js@^3.0.0: - version "3.18.3" - resolved "https://registry.npmjs.org/core-js/-/core-js-3.18.3.tgz#86a0bba2d8ec3df860fefcc07a8d119779f01509" - integrity sha512-tReEhtMReZaPFVw7dajMx0vlsz3oOb8ajgPoHVYGxr8ErnZ6PcYEvvmjGmXlfpnxpkYSdOQttjB+MvVbCGfvLw== - core-js@^3.0.4, core-js@^3.6.5, core-js@^3.8.2: version "3.15.0" resolved "https://registry.npmjs.org/core-js/-/core-js-3.15.0.tgz#db9554ebce0b6fd90dc9b1f2465c841d2d055044" @@ -14248,22 +14243,6 @@ fetch-blob@2.1.2: resolved "https://registry.npmjs.org/fetch-blob/-/fetch-blob-2.1.2.tgz#a7805db1361bd44c1ef62bb57fb5fe8ea173ef3c" integrity sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow== -fetch-mock@^9.11.0: - version "9.11.0" - resolved "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.11.0.tgz#371c6fb7d45584d2ae4a18ee6824e7ad4b637a3f" - integrity sha512-PG1XUv+x7iag5p/iNHD4/jdpxL9FtVSqRMUQhPab4hVDt80T1MH5ehzVrL2IdXO9Q2iBggArFvPqjUbHFuI58Q== - dependencies: - "@babel/core" "^7.0.0" - "@babel/runtime" "^7.0.0" - core-js "^3.0.0" - debug "^4.1.1" - glob-to-regexp "^0.4.0" - is-subset "^0.1.1" - lodash.isequal "^4.5.0" - path-to-regexp "^2.2.1" - querystring "^0.2.0" - whatwg-url "^6.5.0" - fetch-readablestream@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/fetch-readablestream/-/fetch-readablestream-0.2.0.tgz#eaa6d1a76b12de2d4731a343393c6ccdcfe2c795" @@ -15088,7 +15067,7 @@ glob-to-regexp@^0.3.0: resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= -glob-to-regexp@^0.4.0, glob-to-regexp@^0.4.1: +glob-to-regexp@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== @@ -17129,11 +17108,6 @@ is-subdir@^1.1.1: dependencies: better-path-resolve "1.0.0" -is-subset@^0.1.1: - version "0.1.1" - resolved "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" - integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY= - is-svg@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" @@ -19002,7 +18976,7 @@ lodash.isempty@^4.4.0: resolved "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" integrity sha1-b4bL7di+TsmHvpqvM8loTbGzHn4= -lodash.isequal@^4.0.0, lodash.isequal@^4.5.0: +lodash.isequal@^4.0.0: version "4.5.0" resolved "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= @@ -22191,11 +22165,6 @@ path-to-regexp@^1.7.0: dependencies: isarray "0.0.1" -path-to-regexp@^2.2.1: - version "2.4.0" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704" - integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w== - path-type@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -28732,7 +28701,7 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" -whatwg-url@^6.4.1, whatwg-url@^6.5.0: +whatwg-url@^6.4.1: version "6.5.0" resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ== From 5b51907cbf2da15f03a9e9fccd4ec381b80fd9f9 Mon Sep 17 00:00:00 2001 From: Matto Date: Fri, 29 Oct 2021 14:56:31 +1100 Subject: [PATCH 24/65] resolved conflicts Signed-off-by: Matto --- packages/backend-common/src/config.ts | 16 +++++++++++++++ packages/config-loader/src/loader.ts | 29 ++++----------------------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/packages/backend-common/src/config.ts b/packages/backend-common/src/config.ts index 9b0eb8dcd0..5b09d66608 100644 --- a/packages/backend-common/src/config.ts +++ b/packages/backend-common/src/config.ts @@ -84,51 +84,66 @@ export class ObservableConfigProxy implements Config { has(key: string): boolean { return this.select(false)?.has(key) ?? false; } + keys(): string[] { return this.select(false)?.keys() ?? []; } + get(key?: string): T { return this.select(true).get(key); } + getOptional(key?: string): T | undefined { return this.select(false)?.getOptional(key); } + getConfig(key: string): Config { return new ObservableConfigProxy(this.logger, this, key); } + getOptionalConfig(key: string): Config | undefined { if (this.select(false)?.has(key)) { return new ObservableConfigProxy(this.logger, this, key); } return undefined; } + getConfigArray(key: string): Config[] { return this.select(true).getConfigArray(key); } + getOptionalConfigArray(key: string): Config[] | undefined { return this.select(false)?.getOptionalConfigArray(key); } + getNumber(key: string): number { return this.select(true).getNumber(key); } + getOptionalNumber(key: string): number | undefined { return this.select(false)?.getOptionalNumber(key); } + getBoolean(key: string): boolean { return this.select(true).getBoolean(key); } + getOptionalBoolean(key: string): boolean | undefined { return this.select(false)?.getOptionalBoolean(key); } + getString(key: string): string { return this.select(true).getString(key); } + getOptionalString(key: string): string | undefined { return this.select(false)?.getOptionalString(key); } + getStringArray(key: string): string[] { return this.select(true).getStringArray(key); } + getOptionalStringArray(key: string): string[] | undefined { return this.select(false)?.getOptionalStringArray(key); } @@ -184,6 +199,7 @@ export async function loadBackendConfig(options: { } }), }, + remote: { reloadIntervalSeconds: 10 }, }); options.logger.info( diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index 338f199132..b8fab12917 100644 --- a/packages/config-loader/src/loader.ts +++ b/packages/config-loader/src/loader.ts @@ -24,7 +24,6 @@ import { applyConfigTransforms, createIncludeTransform, createSubstitutionTransform, - EnvFunc, isValidUrl, readEnvConfig, } from './lib'; @@ -51,29 +50,6 @@ export type LoadConfigOptionsRemote = { reloadIntervalSeconds: number; }; -/** @public */ -export type RemoteConfigProp = { - /** - * URL of the remote config - */ - url: string; - - /** - * Contents of the remote config - */ - content: string | null; - - /** - * An optional new ETag header value. Used when checking for updated config. - */ - newETag?: string; - - /** - * An optional old ETag header value. Used when checking for updated config - */ - oldETag?: string; -}; - /** * Options that control the loading of configuration files in the backend. * @@ -229,7 +205,10 @@ export async function loadConfig( try { remoteConfigs = await loadRemoteConfigFiles(); } catch (error) { - throw new ForwardedError(`Failed to read remote configuration file, ${error}`); + throw new ForwardedError( + `Failed to read remote configuration file`, + error, + ); } } From 5cce78f968099c3701303fbb035918c8914e50c0 Mon Sep 17 00:00:00 2001 From: Matto Date: Fri, 29 Oct 2021 15:11:54 +1100 Subject: [PATCH 25/65] Resolved conflicts Signed-off-by: Matto --- .changeset/giant-years-help.md | 1 - ADOPTERS.md | 122 +++++++++++++------------- packages/backend-common/src/config.ts | 15 ---- packages/cli/package.json | 4 - packages/config-loader/package.json | 1 - packages/integration/api-report.md | 5 -- packages/integration/src/index.ts | 1 - 7 files changed, 61 insertions(+), 88 deletions(-) diff --git a/.changeset/giant-years-help.md b/.changeset/giant-years-help.md index f0ca12ef19..c6ca0def30 100644 --- a/.changeset/giant-years-help.md +++ b/.changeset/giant-years-help.md @@ -2,7 +2,6 @@ '@backstage/backend-common': patch '@backstage/cli': patch '@backstage/config-loader': patch -'@backstage/integration': patch --- Reading app config from a remote server diff --git a/ADOPTERS.md b/ADOPTERS.md index 2d2583be95..db751bfb9b 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -1,61 +1,61 @@ -| Organization | Contact | Description of Use | -| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. | -| [bol.com](https://www.bol.com) | [@sagacity](https://github.com/sagacity) | Initial work being done to unify platform tooling. | -| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | -| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up | -| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | -| [SDA SE](https://sda.se) | [@Fox32](https://github.com/Fox32) | Central place for developing and sharing services in our insurance ecosystem. | -| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. | -| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications | -| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. | -| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | -| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D | -| [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling | -| [Grab](https://www.grab.com) | [@althafh](https://github.com/althafh) | Initial work as a unified interface for all of Grab's internal tooling | -| [Telenor Sweden](https://www.telenor.se) | [@O5ten](https://github.com/O5ten) | Building a developer portal for scaffolding projects towards our unified build environment and microservice stacks | -| [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. | -| [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. | -| [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. | -| [Expedia Group](https://www.expediagroup.com) | [Mike Turner](mailto:miturner@expediagroup.com), [Sneha Kumar](mailto:snkumar@expediagroup.com), [@guillermomanzo](https://github.com/guillermomanzo), [Erik Lindgren](https://github.com/lindgren) | EG Common Developer Toolkit | -| [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go | -| [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling | -| [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. | -| [Trendyol](https://trendyol.com) | [Gamze Senturk](https://github.com/gmzsenturk), [Mert Can Bilgic](https://github.com/mertcb) | The Developer Portal has been called `Pandora`. Provides an overview of Trendyol tech ecosystem. TechDocs, Catalog, Custom Plugins and Theme. | -| [Peloton](https://www.onepeloton.com/) | [Jim Haughwout](https://github.com/JimHaughwout) | Creating our first developer portal and tech-docs. Exploring Service Catalog, Tech Insights and Cost Insights as well. | -| [TELUS](https://telus.com) | [Seb Barre](https://github.com/sbarre) | The Go-to place to find answers about development and delivery at TELUS. | -| [Brex](https://www.brex.com/) | [Vamsi Chitters](https://github.com/vamsikc) | A centralized UI to understand how a service fits in the whole Brex architecture and manage a team’s engineering dependencies. | -| [Oriflame](https://www.oriflame.com/) | [Oriflame](https://github.com/oriflame) | Internal developer portal for services, single page apps and packages overview, API documentation, technical guides, tech-radar and more. | -| [Booz Allen Hamilton](https://www.boozallen.com/) | [Jason Miller](https://github.com/JasonMiller-BAH) | Developer portal for a full-stack software development ecosystem that accelerates consistent and repeatable Modern Software Development practices for internal innovation and investments. | -| [Netflix](https://www.netflix.com/) | [bleathem](https://github.com/bleathem) | Our Backstage implementation will be the front door to a unified experience connecting our internal platform products across important workflows with integrated knowledge and support. | -| [b.well](https://www.icanbwell.com/) | [Jacob Rosales](https://github.com/jrosales) | Foundation for our engineering portal and cloud insights. | -| [PagerDuty](https://www.pagerduty.com/) | [Mark Shaw](https://github.com/markshawtoronto) | Developer portal, initially focused on software templates and tech-docs. | -| [MoonShiner](https://moonshiner.at) | [Fabian Hippmann](https://github.com/FabianHippmann) | Developer portal - helps us keep track of our customer projects, onboard new developers & improve our development process 🌕🚀🧑‍🚀 | -| [FundApps](https://www.fundapps.co/) | [Elliot Greenwood](https://github.com/egnwd) | Developer Portal - A place for us to keep track of our projects and documentation for all services and processes | -| [DAZN](https://dazn.com/) | [Lou Bichard](https://twitter.com/loujaybee), [Marco Crivellaro](https://github.com/crivetechie), [Alex Hollerith](mailto:alex.hollerith@dazn.com) | Ingesting all of DAZN's repos for the catalog, migrating our internal platform apps (pull request boards, release information, inner source marketplace etc) to Backstage plugins (where applicable). | -| [HelloFresh](https://www.hellofresh.de/) | [@iammuho](https://github.com/iammuho), [@ElenaForester](https://github.com/ElenaForester), [@diegomarangoni](https://github.com/diegomarangoni) | Our developer portal at HelloFresh - Spread across an organisation of 500+ engineers globally. | -| [FactSet](https://www.factset.com/) | [@kuangp](https://github.com/kuangp) | Developer portal to provide discoverability to all internal components, APIs, documentation, and scaffold templates with integrations to our internal infrastructure tools. | -| [Workrise](https://www.workrise.com/) | [Michael Rode](https://github.com/michaelrode) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | -| [RedVentures](https://www.redventures.com/) | [Chris Diaz](https://github.com/codingdiaz) | Developer portal that brings everything an engineer needs to provide value into a single pane of glass. | -| [MavTek](https://www.mavtek.com/) | [@fgascon](https://github.com/fgascon) | Developer portal focused on standardizing practices, centralizing documentation and streamlining developer practices. | -| [QuintoAndar](https://www.quintoandar.com.br/) | [@quintoandar](https://github.com/quintoandar) | Developer portal, services catalog and centralization of service metrics. | -| [empathy.co](https://empathy.co/) | [@guillermotti](https://github.com/guillermotti) | Developer portal for tech docs, service catalog, plugin discovery and much more. | -| [creditas.com](https://creditas.com/) | [@aureliosaraiva](https://github.com/aureliosaraiva) [@Creditas](https://github.com/creditas) | Centralization of all services, standards, documentation, etc. We started the deployment process. | -| [Prisjakt](https://www.prisjakt.nu) / [PriceSpy](https://pricespy.co.uk) | [@kennylindahl](https://github.com/kennylindahl) | Internal developer portal - Documentation, scaffolding, software catalog, TechRadar, Gitlab org data integration | -| [Powerspike](https://powerspike.tv/) | [@trelore](https://github.com/trelore) | Developer portal for documentation of core libraries and repositories. | -| [2U](https://2u.com) | [Andrew Thal](https://github.com/athal7) | Development team home-base, promoting service discoverability, resource dependencies, and tech radar | -| [Taxfix](https://taxfix.de/) | [Sami Ur Rehman](https://github.com/samiurrehman92) | Developer's portal with software catalog at it's core. Hosts API Specs, Tech Docs, Tech Radar and some custom plugins. | -| [Busuu](https://busuu.com/) | [Adam Tester](https://github.com/adamtester) | Developer portal with service catalog, API docs, Event docs, service templating, and cost insights. | -| [Loadsmart](https://loadsmart.com/) | [Loadsmart](https://github.com/loadsmart) | Improve services visibility and operations for service owners and developers. | -| [Monzo](https://monzo.com/) | [@WillSewell](https://github.com/WillSewell), [@joechrisellis](https://github.com/joechrisellis) | Developer portal showing metadata and docs for over 2000 microservices. We have built a number of plugins such as a UI for our system to measure [software excellence](https://monzo.com/blog/2021/09/15/how-we-measure-software-excellence), and a UI to show deployment and config change events. | -| [Vaimo](https://www.vaimo.com) | [@vaimo-magnus](https://github.com/vaimo-magnus) | Developer Portal for our developers at Vaimo, currently docs and self-service towards our internal PaaS based on k8s. Plans to extend the catalog into Projects, Environments etc | -| [Wayfair](https://www.wayfair.com) | [@fransan](https://github.com/fransan), [@errskipower](https://github.com/errskipower), [@hrrs](https://github.com/hrrs) | Developer portal for service catalog, technical documentation, and APIs. | -| [CircleHD](https://www.circlehd.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe | -| [CastDesk](https://castdesk.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe | -| [Santagostino](https://santagostino.it) | [@santagostino](https://github.com/santagostino) | Developer portal, gateway to our infrastructure, documentation, service catalog and internal tooling. | -| [Peak](https://peak.ai) | [Luke Beamish](https://github.com/lukebeamish-peak) | Developer portal for all internal engineers to access documentation and tooling. | -| [Gelato](https://gelato.com/) | [Dmitry Makarenko](https://github.com/dmitry-makarenko-gelato) | Developer portal: documentation, service templates, org structure, service catalog, plugins for integration with internal and third-party systems🚀. | -| [GoCardless](https://gocardless.com/) | [James Turley](https://github.com/tragiclifestories) | Developer portal: documentation, service templates, org structure, service catalog, plugins for integration with internal systems. | -| [Box](https://www.box.com) | [@kielosz](https://github.com/kielosz), [@jluk-box](https://github.com/jluk-box), [@ptychu](https://github.com/ptychu), [@alexrybch](https://github.com/alexrybch), [@szubster](https://github.com/szubster) | Developer portal for service catalog, integration with internal systems, new service onboarding. | -| [Bazaarvoice](https://www.bazaarvoice.com) | [@niallmccullagh](https://github.com/niallmccullagh) | Developer portal for service catalog and scaffolds, publishing Github docs and API documentation, visualising our internal tech radar and our product engineering org structure. | -| [Krateo PlatformOps](https://www.krateo.io) | [@projectkerberus](https://github.com/projectkerberus) | A multi-cloud control plane to create, manage and deploy any kind of resource easily and centrally via a Developer Portal that centralizes via a self-service catalog the templating and ownership of services, the available documentation, the overview of the components that compose an entire domain and all the data of the service lifecycle. | +| Organization | Contact | Description of Use | +| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. | +| [bol.com](https://www.bol.com) | [@sagacity](https://github.com/sagacity) | Initial work being done to unify platform tooling. | +| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | +| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up | +| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | +| [SDA SE](https://sda.se) | [@Fox32](https://github.com/Fox32) | Central place for developing and sharing services in our insurance ecosystem. | +| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. | +| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications | +| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. | +| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | +| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D | +| [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling | +| [Grab](https://www.grab.com) | [@althafh](https://github.com/althafh) | Initial work as a unified interface for all of Grab's internal tooling | +| [Telenor Sweden](https://www.telenor.se) | [@O5ten](https://github.com/O5ten) | Building a developer portal for scaffolding projects towards our unified build environment and microservice stacks | +| [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. | +| [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. | +| [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. | +| [Expedia Group](https://www.expediagroup.com) | [Mike Turner](mailto:miturner@expediagroup.com), [Sneha Kumar](mailto:snkumar@expediagroup.com), [@guillermomanzo](https://github.com/guillermomanzo), [Erik Lindgren](https://github.com/lindgren) | EG Common Developer Toolkit | +| [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go | +| [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling | +| [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. | +| [Trendyol](https://trendyol.com) | [Gamze Senturk](https://github.com/gmzsenturk), [Mert Can Bilgic](https://github.com/mertcb) | The Developer Portal has been called `Pandora`. Provides an overview of Trendyol tech ecosystem. TechDocs, Catalog, Custom Plugins and Theme. | +| [Peloton](https://www.onepeloton.com/) | [Jim Haughwout](https://github.com/JimHaughwout) | Creating our first developer portal and tech-docs. Exploring Service Catalog, Tech Insights and Cost Insights as well. | +| [TELUS](https://telus.com) | [Seb Barre](https://github.com/sbarre) | The Go-to place to find answers about development and delivery at TELUS. | +| [Brex](https://www.brex.com/) | [Vamsi Chitters](https://github.com/vamsikc) | A centralized UI to understand how a service fits in the whole Brex architecture and manage a team’s engineering dependencies. | +| [Oriflame](https://www.oriflame.com/) | [Oriflame](https://github.com/oriflame) | Internal developer portal for services, single page apps and packages overview, API documentation, technical guides, tech-radar and more. | +| [Booz Allen Hamilton](https://www.boozallen.com/) | [Jason Miller](https://github.com/JasonMiller-BAH) | Developer portal for a full-stack software development ecosystem that accelerates consistent and repeatable Modern Software Development practices for internal innovation and investments. | +| [Netflix](https://www.netflix.com/) | [bleathem](https://github.com/bleathem) | Our Backstage implementation will be the front door to a unified experience connecting our internal platform products across important workflows with integrated knowledge and support. | +| [b.well](https://www.icanbwell.com/) | [Jacob Rosales](https://github.com/jrosales) | Foundation for our engineering portal and cloud insights. | +| [PagerDuty](https://www.pagerduty.com/) | [Mark Shaw](https://github.com/markshawtoronto) | Developer portal, initially focused on software templates and tech-docs. | +| [MoonShiner](https://moonshiner.at) | [Fabian Hippmann](https://github.com/FabianHippmann) | Developer portal - helps us keep track of our customer projects, onboard new developers & improve our development process 🌕🚀🧑‍🚀 | +| [FundApps](https://www.fundapps.co/) | [Elliot Greenwood](https://github.com/egnwd) | Developer Portal - A place for us to keep track of our projects and documentation for all services and processes | +| [DAZN](https://dazn.com/) | [Lou Bichard](https://twitter.com/loujaybee), [Marco Crivellaro](https://github.com/crivetechie), [Alex Hollerith](mailto:alex.hollerith@dazn.com) | Ingesting all of DAZN's repos for the catalog, migrating our internal platform apps (pull request boards, release information, inner source marketplace etc) to Backstage plugins (where applicable). | +| [HelloFresh](https://www.hellofresh.de/) | [@iammuho](https://github.com/iammuho), [@ElenaForester](https://github.com/ElenaForester), [@diegomarangoni](https://github.com/diegomarangoni) | Our developer portal at HelloFresh - Spread across an organisation of 500+ engineers globally. | +| [FactSet](https://www.factset.com/) | [@kuangp](https://github.com/kuangp) | Developer portal to provide discoverability to all internal components, APIs, documentation, and scaffold templates with integrations to our internal infrastructure tools. | +| [Workrise](https://www.workrise.com/) | [Michael Rode](https://github.com/michaelrode) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | +| [RedVentures](https://www.redventures.com/) | [Chris Diaz](https://github.com/codingdiaz) | Developer portal that brings everything an engineer needs to provide value into a single pane of glass. | +| [MavTek](https://www.mavtek.com/) | [@fgascon](https://github.com/fgascon) | Developer portal focused on standardizing practices, centralizing documentation and streamlining developer practices. | +| [QuintoAndar](https://www.quintoandar.com.br/) | [@quintoandar](https://github.com/quintoandar) | Developer portal, services catalog and centralization of service metrics. | +| [empathy.co](https://empathy.co/) | [@guillermotti](https://github.com/guillermotti) | Developer portal for tech docs, service catalog, plugin discovery and much more. | +| [creditas.com](https://creditas.com/) | [@aureliosaraiva](https://github.com/aureliosaraiva) [@Creditas](https://github.com/creditas) | Centralization of all services, standards, documentation, etc. We started the deployment process. | +| [Prisjakt](https://www.prisjakt.nu) / [PriceSpy](https://pricespy.co.uk) | [@kennylindahl](https://github.com/kennylindahl) | Internal developer portal - Documentation, scaffolding, software catalog, TechRadar, Gitlab org data integration | +| [Powerspike](https://powerspike.tv/) | [@trelore](https://github.com/trelore) | Developer portal for documentation of core libraries and repositories. | +| [2U](https://2u.com) | [Andrew Thal](https://github.com/athal7) | Development team home-base, promoting service discoverability, resource dependencies, and tech radar | +| [Taxfix](https://taxfix.de/) | [Sami Ur Rehman](https://github.com/samiurrehman92) | Developer's portal with software catalog at it's core. Hosts API Specs, Tech Docs, Tech Radar and some custom plugins. | +| [Busuu](https://busuu.com/) | [Adam Tester](https://github.com/adamtester) | Developer portal with service catalog, API docs, Event docs, service templating, and cost insights. | +| [Loadsmart](https://loadsmart.com/) | [Loadsmart](https://github.com/loadsmart) | Improve services visibility and operations for service owners and developers. | +| [Monzo](https://monzo.com/) | [@WillSewell](https://github.com/WillSewell), [@joechrisellis](https://github.com/joechrisellis) | Developer portal showing metadata and docs for over 2000 microservices. We have built a number of plugins such as a UI for our system to measure [software excellence](https://monzo.com/blog/2021/09/15/how-we-measure-software-excellence), and a UI to show deployment and config change events. | +| [Vaimo](https://www.vaimo.com) | [@vaimo-magnus](https://github.com/vaimo-magnus) | Developer Portal for our developers at Vaimo, currently docs and self-service towards our internal PaaS based on k8s. Plans to extend the catalog into Projects, Environments etc | +| [Wayfair](https://www.wayfair.com) | [@fransan](https://github.com/fransan), [@errskipower](https://github.com/errskipower), [@hrrs](https://github.com/hrrs) | Developer portal for service catalog, technical documentation, and APIs. | +| [CircleHD](https://www.circlehd.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe | +| [CastDesk](https://castdesk.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe | +| [Santagostino](https://santagostino.it) | [@santagostino](https://github.com/santagostino) | Developer portal, gateway to our infrastructure, documentation, service catalog and internal tooling. | +| [Peak](https://peak.ai) | [Luke Beamish](https://github.com/lukebeamish-peak) | Developer portal for all internal engineers to access documentation and tooling. | +| [Gelato](https://gelato.com/) | [Dmitry Makarenko](https://github.com/dmitry-makarenko-gelato) | Developer portal: documentation, service templates, org structure, service catalog, plugins for integration with internal and third-party systems🚀. | +| [GoCardless](https://gocardless.com/) | [James Turley](https://github.com/tragiclifestories) | Developer portal: documentation, service templates, org structure, service catalog, plugins for integration with internal systems. | +| [Box](https://www.box.com) | [@kielosz](https://github.com/kielosz), [@jluk-box](https://github.com/jluk-box), [@ptychu](https://github.com/ptychu), [@alexrybch](https://github.com/alexrybch), [@szubster](https://github.com/szubster) | Developer portal for service catalog, integration with internal systems, new service onboarding. | +| [Bazaarvoice](https://www.bazaarvoice.com) | [@niallmccullagh](https://github.com/niallmccullagh) | Developer portal for service catalog and scaffolds, publishing Github docs and API documentation, visualising our internal tech radar and our product engineering org structure. | +| [Krateo PlatformOps](https://www.krateo.io) | [@projectkerberus](https://github.com/projectkerberus) | A multi-cloud control plane to create, manage and deploy any kind of resource easily and centrally via a Developer Portal that centralizes via a self-service catalog the templating and ownership of services, the available documentation, the overview of the components that compose an entire domain and all the data of the service lifecycle. | diff --git a/packages/backend-common/src/config.ts b/packages/backend-common/src/config.ts index 31869c685a..9b0eb8dcd0 100644 --- a/packages/backend-common/src/config.ts +++ b/packages/backend-common/src/config.ts @@ -84,66 +84,51 @@ export class ObservableConfigProxy implements Config { has(key: string): boolean { return this.select(false)?.has(key) ?? false; } - keys(): string[] { return this.select(false)?.keys() ?? []; } - get(key?: string): T { return this.select(true).get(key); } - getOptional(key?: string): T | undefined { return this.select(false)?.getOptional(key); } - getConfig(key: string): Config { return new ObservableConfigProxy(this.logger, this, key); } - getOptionalConfig(key: string): Config | undefined { if (this.select(false)?.has(key)) { return new ObservableConfigProxy(this.logger, this, key); } return undefined; } - getConfigArray(key: string): Config[] { return this.select(true).getConfigArray(key); } - getOptionalConfigArray(key: string): Config[] | undefined { return this.select(false)?.getOptionalConfigArray(key); } - getNumber(key: string): number { return this.select(true).getNumber(key); } - getOptionalNumber(key: string): number | undefined { return this.select(false)?.getOptionalNumber(key); } - getBoolean(key: string): boolean { return this.select(true).getBoolean(key); } - getOptionalBoolean(key: string): boolean | undefined { return this.select(false)?.getOptionalBoolean(key); } - getString(key: string): string { return this.select(true).getString(key); } - getOptionalString(key: string): string | undefined { return this.select(false)?.getOptionalString(key); } - getStringArray(key: string): string[] { return this.select(true).getStringArray(key); } - getOptionalStringArray(key: string): string[] | undefined { return this.select(false)?.getOptionalStringArray(key); } diff --git a/packages/cli/package.json b/packages/cli/package.json index b9ba9fe8a3..62a008087e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -29,14 +29,10 @@ }, "dependencies": { "@backstage/cli-common": "^0.1.4", - "@babel/core": "^7.4.4", - "@babel/plugin-transform-modules-commonjs": "^7.4.4", - "@backstage/integration": "^0.6.5", "@backstage/config": "^0.1.10", "@backstage/config-loader": "^0.7.0", "@backstage/errors": "^0.1.3", "@backstage/types": "^0.1.1", - "@backstage/config-loader": "^0.6.8", "@hot-loader/react-dom": "^16.13.0", "@lerna/package-graph": "^4.0.0", "@lerna/project": "^4.0.0", diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 58b0061996..efcf753295 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -34,7 +34,6 @@ "@backstage/config": "^0.1.9", "@backstage/errors": "^0.1.3", "@backstage/types": "^0.1.1", - "@backstage/backend-common": "^0.9.4", "@types/json-schema": "^7.0.6", "ajv": "^7.0.3", "chokidar": "^3.5.2", diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index e07f61eea6..01f84383fe 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -347,11 +347,6 @@ export type GoogleGcsIntegrationConfig = { privateKey?: string; }; -// Warning: (ae-missing-release-tag) "isValidUrl" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export function isValidUrl(url: string): boolean; - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (ae-missing-release-tag) "readAwsS3IntegrationConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/packages/integration/src/index.ts b/packages/integration/src/index.ts index b8992f52d2..3d5f336e8d 100644 --- a/packages/integration/src/index.ts +++ b/packages/integration/src/index.ts @@ -27,7 +27,6 @@ export * from './gitlab'; export * from './googleGcs'; export * from './awsS3'; export { defaultScmResolveUrl } from './helpers'; -export { isValidUrl } from './helpers'; export { ScmIntegrations } from './ScmIntegrations'; export type { ScmIntegration, ScmIntegrationsGroup } from './types'; export type { ScmIntegrationRegistry } from './registry'; From f7cd672c7f655427162a7ca0260e3438742537d8 Mon Sep 17 00:00:00 2001 From: Matto Date: Fri, 29 Oct 2021 16:39:13 +1100 Subject: [PATCH 26/65] Updated error message Signed-off-by: Matto --- packages/config-loader/src/loader.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index b8fab12917..4a5660f46c 100644 --- a/packages/config-loader/src/loader.ts +++ b/packages/config-loader/src/loader.ts @@ -116,9 +116,7 @@ export async function loadConfig( .map(configTarget => configTarget.url); if (remote === undefined && configUrls.length > 0) { - throw new Error( - `Remote config detected, however, this feature is turned off. Remote config will be ignored.`, - ); + throw new Error(`Remote config detected but this feature is turned off`); } // If no paths are provided, we default to reading From 7c5f6a0400c069f44aa8d2d3827dcbf6a8d74932 Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Fri, 29 Oct 2021 16:15:07 +0200 Subject: [PATCH 27/65] added comments Signed-off-by: Alex Rybchenko --- .changeset/real-mails-add.md | 2 +- .../core-components/src/components/TabbedLayout/RoutedTabs.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.changeset/real-mails-add.md b/.changeset/real-mails-add.md index 15cdc2997f..2769fef898 100644 --- a/.changeset/real-mails-add.md +++ b/.changeset/real-mails-add.md @@ -3,4 +3,4 @@ '@backstage/plugin-catalog': patch --- -fixed route resolving +fixed route resolving (ssue #7741) when user cannot select a tab in any of the tabbed pages (like the Catalog page) if it shares the same initial letters as a preceding tab. (i.e. where tab with a path of /ci is followed by a path of /cid, user cannot select /cid as /ci will always be selected first). diff --git a/packages/core-components/src/components/TabbedLayout/RoutedTabs.tsx b/packages/core-components/src/components/TabbedLayout/RoutedTabs.tsx index d0c36e286c..c5c771b739 100644 --- a/packages/core-components/src/components/TabbedLayout/RoutedTabs.tsx +++ b/packages/core-components/src/components/TabbedLayout/RoutedTabs.tsx @@ -35,6 +35,7 @@ export function useSelectedSubRoute(subRoutes: SubRoute[]): { // TODO: remove once react-router updated const sortedRoutes = routes.sort((a, b) => + // remove added "/*" symbols from path before comparing b.path.slice(0, -2).localeCompare(a.path.slice(0, -2)), ); From a0a66000607f14dfd4571b86ea80115e47b9e766 Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Fri, 29 Oct 2021 16:17:47 +0200 Subject: [PATCH 28/65] fix typo Signed-off-by: Alex Rybchenko --- .changeset/real-mails-add.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/real-mails-add.md b/.changeset/real-mails-add.md index 2769fef898..9eff9cbe62 100644 --- a/.changeset/real-mails-add.md +++ b/.changeset/real-mails-add.md @@ -3,4 +3,4 @@ '@backstage/plugin-catalog': patch --- -fixed route resolving (ssue #7741) when user cannot select a tab in any of the tabbed pages (like the Catalog page) if it shares the same initial letters as a preceding tab. (i.e. where tab with a path of /ci is followed by a path of /cid, user cannot select /cid as /ci will always be selected first). +fixed route resolving (issue #7741) when user cannot select a tab in any of the tabbed pages (like the Catalog page) if it shares the same initial letters as a preceding tab. (i.e. where tab with a path of /ci is followed by a path of /cid, user cannot select /cid as /ci will always be selected first). From 311d604bea6d5a7885942ca1adfbec15f7a4cf72 Mon Sep 17 00:00:00 2001 From: rodion Date: Sun, 31 Oct 2021 22:43:24 +0300 Subject: [PATCH 29/65] fix: plugin sentry id token / private method Signed-off-by: rodion --- plugins/sentry/api-report.md | 11 ----------- plugins/sentry/src/api/production-api.ts | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/plugins/sentry/api-report.md b/plugins/sentry/api-report.md index 1b7a248e48..6d83ddc5e6 100644 --- a/plugins/sentry/api-report.md +++ b/plugins/sentry/api-report.md @@ -41,17 +41,6 @@ export class ProductionSentryApi implements SentryApi { identityApi?: IdentityApi | undefined, ); // (undocumented) - authOptions(): Promise< - | { - headers?: undefined; - } - | { - headers: { - authorization: string; - }; - } - >; - // (undocumented) fetchIssues( project: string, statsFor: string, diff --git a/plugins/sentry/src/api/production-api.ts b/plugins/sentry/src/api/production-api.ts index 37a735df14..1fc6ece37f 100644 --- a/plugins/sentry/src/api/production-api.ts +++ b/plugins/sentry/src/api/production-api.ts @@ -51,7 +51,7 @@ export class ProductionSentryApi implements SentryApi { return (await response.json()) as SentryIssue[]; } - async authOptions() { + private async authOptions() { if (!this.identityApi) { return {}; } From ef01b7c7a7a22623c3fd5e7f85d274842b1cb8db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Nov 2021 04:15:34 +0000 Subject: [PATCH 30/65] build(deps-dev): bump @storybook/addons from 6.3.11 to 6.3.12 Bumps [@storybook/addons](https://github.com/storybookjs/storybook/tree/HEAD/lib/addons) from 6.3.11 to 6.3.12. - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v6.3.12/lib/addons) --- updated-dependencies: - dependency-name: "@storybook/addons" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- yarn.lock | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 3fec636e32..e879cff365 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5531,7 +5531,7 @@ react-syntax-highlighter "^13.5.3" regenerator-runtime "^0.13.7" -"@storybook/addons@6.3.11", "@storybook/addons@^6.1.11": +"@storybook/addons@6.3.11": version "6.3.11" resolved "https://registry.npmjs.org/@storybook/addons/-/addons-6.3.11.tgz#4b5e88793bcce7ef823340e9010a96e35e3284cc" integrity sha512-2Y03lOwzWDRB/glISa/4luBMM5uyYhkIBixbZF9miIb2SCWRlNmom5NCnKsR18Wu6g7zI7os3aAMfKr24aSofQ== @@ -5561,6 +5561,21 @@ global "^4.4.0" regenerator-runtime "^0.13.7" +"@storybook/addons@^6.1.11": + version "6.3.12" + resolved "https://registry.npmjs.org/@storybook/addons/-/addons-6.3.12.tgz#8773dcc113c5086dfff722388b7b65580e43b65b" + integrity sha512-UgoMyr7Qr0FS3ezt8u6hMEcHgyynQS9ucr5mAwZky3wpXRPFyUTmMto9r4BBUdqyUvTUj/LRKIcmLBfj+/l0Fg== + dependencies: + "@storybook/api" "6.3.12" + "@storybook/channels" "6.3.12" + "@storybook/client-logger" "6.3.12" + "@storybook/core-events" "6.3.12" + "@storybook/router" "6.3.12" + "@storybook/theming" "6.3.12" + core-js "^3.8.2" + global "^4.4.0" + regenerator-runtime "^0.13.7" + "@storybook/api@6.3.11": version "6.3.11" resolved "https://registry.npmjs.org/@storybook/api/-/api-6.3.11.tgz#ea3806a0570da65bfb5b39e4edb90289b5ba701e" @@ -5587,6 +5602,32 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/api@6.3.12": + version "6.3.12" + resolved "https://registry.npmjs.org/@storybook/api/-/api-6.3.12.tgz#2845c20464d5348d676d09665e8ab527825ed7b5" + integrity sha512-LScRXUeCWEW/OP+jiooNMQICVdusv7azTmULxtm72fhkXFRiQs2CdRNTiqNg46JLLC9z95f1W+pGK66X6HiiQA== + dependencies: + "@reach/router" "^1.3.4" + "@storybook/channels" "6.3.12" + "@storybook/client-logger" "6.3.12" + "@storybook/core-events" "6.3.12" + "@storybook/csf" "0.0.1" + "@storybook/router" "6.3.12" + "@storybook/semver" "^7.3.2" + "@storybook/theming" "6.3.12" + "@types/reach__router" "^1.3.7" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.20" + memoizerific "^1.11.3" + qs "^6.10.0" + regenerator-runtime "^0.13.7" + store2 "^2.12.0" + telejson "^5.3.2" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/api@6.3.7": version "6.3.7" resolved "https://registry.npmjs.org/@storybook/api/-/api-6.3.7.tgz#88b8a51422cd0739c91bde0b1d65fb6d8a8485d0" @@ -5724,6 +5765,15 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/channels@6.3.12": + version "6.3.12" + resolved "https://registry.npmjs.org/@storybook/channels/-/channels-6.3.12.tgz#aa0d793895a8b211f0ad3459c61c1bcafd0093c7" + integrity sha512-l4sA+g1PdUV8YCbgs47fIKREdEQAKNdQIZw0b7BfTvY9t0x5yfBywgQhYON/lIeiNGz2OlIuD+VUtqYfCtNSyw== + dependencies: + core-js "^3.8.2" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/channels@6.3.7": version "6.3.7" resolved "https://registry.npmjs.org/@storybook/channels/-/channels-6.3.7.tgz#85ed5925522b802d959810f78d37aacde7fea66e" @@ -5789,6 +5839,14 @@ core-js "^3.8.2" global "^4.4.0" +"@storybook/client-logger@6.3.12": + version "6.3.12" + resolved "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.3.12.tgz#6585c98923b49fcb25dbceeeb96ef2a83e28e0f4" + integrity sha512-zNDsamZvHnuqLznDdP9dUeGgQ9TyFh4ray3t1VGO7ZqWVZ2xtVCCXjDvMnOXI2ifMpX5UsrOvshIPeE9fMBmiQ== + dependencies: + core-js "^3.8.2" + global "^4.4.0" + "@storybook/client-logger@6.3.7": version "6.3.7" resolved "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.3.7.tgz#ff17b7494e7e9e23089b0d5c5364c371c726bdd1" @@ -5941,6 +5999,13 @@ dependencies: core-js "^3.8.2" +"@storybook/core-events@6.3.12": + version "6.3.12" + resolved "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.3.12.tgz#73f6271d485ef2576234e578bb07705b92805290" + integrity sha512-SXfD7xUUMazaeFkB92qOTUV8Y/RghE4SkEYe5slAdjeocSaH7Nz2WV0rqNEgChg0AQc+JUI66no8L9g0+lw4Gw== + dependencies: + core-js "^3.8.2" + "@storybook/core-events@6.3.7": version "6.3.7" resolved "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.3.7.tgz#c5bc7cae7dc295de73b6b9f671ecbe582582e9bd" @@ -6135,6 +6200,22 @@ qs "^6.10.0" ts-dedent "^2.0.0" +"@storybook/router@6.3.12": + version "6.3.12" + resolved "https://registry.npmjs.org/@storybook/router/-/router-6.3.12.tgz#0d572ec795f588ca886f39cb9b27b94ff3683f84" + integrity sha512-G/pNGCnrJRetCwyEZulHPT+YOcqEj/vkPVDTUfii2qgqukup6K0cjwgd7IukAURnAnnzTi1gmgFuEKUi8GE/KA== + dependencies: + "@reach/router" "^1.3.4" + "@storybook/client-logger" "6.3.12" + "@types/reach__router" "^1.3.7" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.20" + memoizerific "^1.11.3" + qs "^6.10.0" + ts-dedent "^2.0.0" + "@storybook/router@6.3.7": version "6.3.7" resolved "https://registry.npmjs.org/@storybook/router/-/router-6.3.7.tgz#1714a99a58a7b9f08b6fcfe2b678dad6ca896736" @@ -6193,6 +6274,24 @@ resolve-from "^5.0.0" ts-dedent "^2.0.0" +"@storybook/theming@6.3.12": + version "6.3.12" + resolved "https://registry.npmjs.org/@storybook/theming/-/theming-6.3.12.tgz#5bddf9bd90a60709b5ab238ecdb7d9055dd7862e" + integrity sha512-wOJdTEa/VFyFB2UyoqyYGaZdym6EN7RALuQOAMT6zHA282FBmKw8nL5DETHEbctpnHdcrMC/391teK4nNSrdOA== + dependencies: + "@emotion/core" "^10.1.1" + "@emotion/is-prop-valid" "^0.8.6" + "@emotion/styled" "^10.0.27" + "@storybook/client-logger" "6.3.12" + core-js "^3.8.2" + deep-object-diff "^1.1.0" + emotion-theming "^10.0.27" + global "^4.4.0" + memoizerific "^1.11.3" + polished "^4.0.5" + resolve-from "^5.0.0" + ts-dedent "^2.0.0" + "@storybook/theming@6.3.7": version "6.3.7" resolved "https://registry.npmjs.org/@storybook/theming/-/theming-6.3.7.tgz#6daf9a21b26ed607f3c28a82acd90c0248e76d8b" From 290fbb3ec2c6fbd4a1425e6eb906a35e4d879999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarek=20=C5=81ukow?= Date: Mon, 1 Nov 2021 11:17:00 +0100 Subject: [PATCH 31/65] Improve API docs in Scaffolder action plugins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jarek Łukow --- .changeset/wicked-boats-lie.md | 7 +++++++ .../api-report.md | 4 +--- .../src/actions/fetch/cookiecutter.ts | 9 +++++++++ .../src/index.ts | 2 +- plugins/scaffolder-backend-module-rails/api-report.md | 4 +--- .../src/actions/fetch/rails/index.ts | 10 ++++++++++ plugins/scaffolder-backend-module-rails/src/index.ts | 2 +- plugins/scaffolder-backend-module-yeoman/api-report.md | 6 +----- .../src/actions/run/yeoman.ts | 9 +++++++++ plugins/scaffolder-backend-module-yeoman/src/index.ts | 7 +++++++ 10 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 .changeset/wicked-boats-lie.md diff --git a/.changeset/wicked-boats-lie.md b/.changeset/wicked-boats-lie.md new file mode 100644 index 0000000000..8c5da92d9c --- /dev/null +++ b/.changeset/wicked-boats-lie.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch +'@backstage/plugin-scaffolder-backend-module-rails': patch +'@backstage/plugin-scaffolder-backend-module-yeoman': patch +--- + +Add missing API docs to scaffolder action plugins diff --git a/plugins/scaffolder-backend-module-cookiecutter/api-report.md b/plugins/scaffolder-backend-module-cookiecutter/api-report.md index 6fa57fd0f3..132d3d4225 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/api-report.md +++ b/plugins/scaffolder-backend-module-cookiecutter/api-report.md @@ -10,9 +10,7 @@ import { ScmIntegrations } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-backend'; import { UrlReader } from '@backstage/backend-common'; -// Warning: (ae-missing-release-tag) "createFetchCookiecutterAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function createFetchCookiecutterAction(options: { reader: UrlReader; integrations: ScmIntegrations; diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts index 5120cd3b27..c4d7f1e3e8 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts @@ -121,6 +121,15 @@ export class CookiecutterRunner { } } +/** + * Creates a `fetch:cookiecutter` Scaffolder action. + * + * @remarks + * + * See {@link https://cookiecutter.readthedocs.io/} and {@link https://backstage.io/docs/features/software-templates/writing-custom-actions}. + * @param options - Templating configuration. + * @public + */ export function createFetchCookiecutterAction(options: { reader: UrlReader; integrations: ScmIntegrations; diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/index.ts b/plugins/scaffolder-backend-module-cookiecutter/src/index.ts index cce8011059..4f0ba9a407 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/index.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/index.ts @@ -15,7 +15,7 @@ */ /** - * A module for the scaffolder backend that lets you template projects using cookiecutter + * A module for the scaffolder backend that lets you template projects using {@link https://cookiecutter.readthedocs.io/ | cookiecutter}. * * @packageDocumentation */ diff --git a/plugins/scaffolder-backend-module-rails/api-report.md b/plugins/scaffolder-backend-module-rails/api-report.md index 477483f70e..9c0610d4f2 100644 --- a/plugins/scaffolder-backend-module-rails/api-report.md +++ b/plugins/scaffolder-backend-module-rails/api-report.md @@ -8,9 +8,7 @@ import { ScmIntegrations } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-backend'; import { UrlReader } from '@backstage/backend-common'; -// Warning: (ae-missing-release-tag) "createFetchRailsAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function createFetchRailsAction(options: { reader: UrlReader; integrations: ScmIntegrations; diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts index 5ce7d46689..00a5690c0a 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts @@ -27,6 +27,16 @@ import { import { resolve as resolvePath } from 'path'; import { RailsNewRunner } from './railsNewRunner'; +/** + * Creates the `fetch:rails` Scaffolder action. + * + * @remarks + * + * See {@link https://guides.rubyonrails.org/rails_application_templates.html} and {@link https://backstage.io/docs/features/software-templates/writing-custom-actions}. + * + * @param options - Configuration of the templater. + * @public + */ export function createFetchRailsAction(options: { reader: UrlReader; integrations: ScmIntegrations; diff --git a/plugins/scaffolder-backend-module-rails/src/index.ts b/plugins/scaffolder-backend-module-rails/src/index.ts index eb51c06c49..773e1feabd 100644 --- a/plugins/scaffolder-backend-module-rails/src/index.ts +++ b/plugins/scaffolder-backend-module-rails/src/index.ts @@ -15,7 +15,7 @@ */ /** - * A module for the scaffolder backend that lets you template projects using Rails + * A module for the scaffolder backend that lets you template projects using {@link https://guides.rubyonrails.org/rails_application_templates.html | Rails}. * * @packageDocumentation */ diff --git a/plugins/scaffolder-backend-module-yeoman/api-report.md b/plugins/scaffolder-backend-module-yeoman/api-report.md index 1f38d3945c..fc18814faa 100644 --- a/plugins/scaffolder-backend-module-yeoman/api-report.md +++ b/plugins/scaffolder-backend-module-yeoman/api-report.md @@ -5,10 +5,6 @@ ```ts import { TemplateAction } from '@backstage/plugin-scaffolder-backend'; -// Warning: (ae-missing-release-tag) "createRunYeomanAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function createRunYeomanAction(): TemplateAction; - -// (No @packageDocumentation comment for this package) ``` diff --git a/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.ts b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.ts index f6a29037a3..f7b0c54b41 100644 --- a/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.ts +++ b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.ts @@ -18,6 +18,15 @@ import { JsonObject } from '@backstage/types'; import { createTemplateAction } from '@backstage/plugin-scaffolder-backend'; import { yeomanRun } from './yeomanRun'; +/** + * Creates a `run:yeoman` Scaffolder action. + * + * @remarks + * + * See {@link https://yeoman.io/} and {@link https://backstage.io/docs/features/software-templates/writing-custom-actions}. + * + * @public + */ export function createRunYeomanAction() { return createTemplateAction<{ namespace: string; diff --git a/plugins/scaffolder-backend-module-yeoman/src/index.ts b/plugins/scaffolder-backend-module-yeoman/src/index.ts index 4f06b14a86..1aac8e12f9 100644 --- a/plugins/scaffolder-backend-module-yeoman/src/index.ts +++ b/plugins/scaffolder-backend-module-yeoman/src/index.ts @@ -13,4 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +/** + * A module for the scaffolder backend that lets you template projects using + * {@link https://yeoman.io/ | Yeoman}. + * + * @packageDocumentation + */ export * from './actions'; From 8666b7bc903b55810632f2239caadb929616d660 Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Tue, 2 Nov 2021 09:26:45 +0100 Subject: [PATCH 32/65] updated changeset Signed-off-by: Alex Rybchenko --- .changeset/real-mails-add.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.changeset/real-mails-add.md b/.changeset/real-mails-add.md index 9eff9cbe62..30a9100699 100644 --- a/.changeset/real-mails-add.md +++ b/.changeset/real-mails-add.md @@ -1,6 +1,5 @@ --- '@backstage/core-components': patch -'@backstage/plugin-catalog': patch --- -fixed route resolving (issue #7741) when user cannot select a tab in any of the tabbed pages (like the Catalog page) if it shares the same initial letters as a preceding tab. (i.e. where tab with a path of /ci is followed by a path of /cid, user cannot select /cid as /ci will always be selected first). +fixed route resolving (issue #7741) when user cannot select a tab in any of the tabbed pages (like the Catalog page) if it shares the same initial letters as a preceding tab. (i.e. where tab with a path of /ci is followed by a path of /ci-2, user cannot select /ci-2 as /ci will always be selected first). From e3f665804974a122bed8eb19393a0dad145f9d37 Mon Sep 17 00:00:00 2001 From: Dede Hamzah Date: Wed, 3 Nov 2021 14:38:52 +0700 Subject: [PATCH 33/65] Add overrides ui name for sidebar Signed-off-by: Dede Hamzah --- .../src/layout/Sidebar/Items.tsx | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/core-components/src/layout/Sidebar/Items.tsx b/packages/core-components/src/layout/Sidebar/Items.tsx index 883e0395cd..ed089ad6a1 100644 --- a/packages/core-components/src/layout/Sidebar/Items.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.tsx @@ -350,21 +350,30 @@ export function SidebarSearchField(props: SidebarSearchFieldProps) { ); } -export const SidebarSpace = styled('div')({ - flex: 1, -}); +export const SidebarSpace = styled('div')( + { + flex: 1, + }, + { name: 'BackstageSidebarSpace' }, +); -export const SidebarSpacer = styled('div')({ - height: 8, -}); +export const SidebarSpacer = styled('div')( + { + height: 8, + }, + { name: 'BackstageSidebarSpacer' }, +); -export const SidebarDivider = styled('hr')({ - height: 1, - width: '100%', - background: '#383838', - border: 'none', - margin: '12px 0px', -}); +export const SidebarDivider = styled('hr')( + { + height: 1, + width: '100%', + background: '#383838', + border: 'none', + margin: '12px 0px', + }, + { name: 'BackstageSidebarDivider' }, +); const styledScrollbar = (theme: Theme): CreateCSSProperties => ({ overflowY: 'auto', From a39a2105efc929d7184e6ccfc55d0fa92bd94d46 Mon Sep 17 00:00:00 2001 From: Dede Hamzah Date: Wed, 3 Nov 2021 15:40:25 +0700 Subject: [PATCH 34/65] add changeset Signed-off-by: Dede Hamzah --- .changeset/tall-boxes-sit.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tall-boxes-sit.md diff --git a/.changeset/tall-boxes-sit.md b/.changeset/tall-boxes-sit.md new file mode 100644 index 0000000000..a74e0400bd --- /dev/null +++ b/.changeset/tall-boxes-sit.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Add Theme Overrides for Sidebar From 84ace9a29c03e556c4f0d2be7dad7d6e5b3e22fd Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Wed, 3 Nov 2021 09:41:20 +0000 Subject: [PATCH 35/65] docs: Created stories for BuildTable component to document different component states. Signed-off-by: Marley Powell --- .changeset/tough-buckets-explain.md | 5 + plugins/azure-devops/package.json | 1 + .../BuildTable/BuildTable.stories.tsx | 94 +++++++++++++++++++ .../src/components/BuildTable/BuildTable.tsx | 4 +- 4 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 .changeset/tough-buckets-explain.md create mode 100644 plugins/azure-devops/src/components/BuildTable/BuildTable.stories.tsx diff --git a/.changeset/tough-buckets-explain.md b/.changeset/tough-buckets-explain.md new file mode 100644 index 0000000000..a2ca37d516 --- /dev/null +++ b/.changeset/tough-buckets-explain.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-azure-devops': patch +--- + +Simplified queue time calculation in `BuildTable`. diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json index a071205711..1c5e0bba84 100644 --- a/plugins/azure-devops/package.json +++ b/plugins/azure-devops/package.json @@ -31,6 +31,7 @@ "@backstage/core-components": "^0.7.2", "@backstage/core-plugin-api": "^0.1.12", "@backstage/errors": "^0.1.4", + "@backstage/plugin-azure-devops-backend": "^0.1.4", "@backstage/plugin-catalog-react": "^0.6.2", "@backstage/theme": "^0.2.12", "@material-ui/core": "^4.12.2", diff --git a/plugins/azure-devops/src/components/BuildTable/BuildTable.stories.tsx b/plugins/azure-devops/src/components/BuildTable/BuildTable.stories.tsx new file mode 100644 index 0000000000..609466769b --- /dev/null +++ b/plugins/azure-devops/src/components/BuildTable/BuildTable.stories.tsx @@ -0,0 +1,94 @@ +/* + * Copyright 2021 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 { + BuildResult, + BuildStatus, + RepoBuild, +} from '@backstage/plugin-azure-devops-backend'; + +import { BuildTable } from './BuildTable'; +import { MemoryRouter } from 'react-router'; +import React from 'react'; + +export default { + title: 'Plugins/Azure Devops/Build Table', + component: BuildTable, +}; + +const buildStatuses: Array<[BuildStatus, BuildResult]> = [ + [BuildStatus.InProgress, BuildResult.None], // In Progress + [BuildStatus.Completed, BuildResult.Succeeded], // Succeeded + [BuildStatus.Completed, BuildResult.Failed], // Failed + [BuildStatus.Completed, BuildResult.PartiallySucceeded], // Partially Succeeded + [BuildStatus.Completed, BuildResult.Canceled], // Cancelled + [BuildStatus.Completed, BuildResult.None], // Unknown + [BuildStatus.Cancelling, BuildResult.None], // Cancelling + [BuildStatus.Postponed, BuildResult.None], // Postponed + [BuildStatus.NotStarted, BuildResult.None], // Not Started + [BuildStatus.None, BuildResult.None], // Unknown +]; + +const generateTestData = (rows = 10): RepoBuild[] => { + const repoBuilds: RepoBuild[] = []; + + for (let i = 0; i < rows; i++) { + const [status, result] = buildStatuses[i] ?? [ + BuildStatus.Completed, + BuildResult.Succeeded, + ]; + + repoBuilds.push({ + id: rows - i + 12534, + title: `backstage ci - 1.0.0-preview-${rows - i}`, + status, + result, + queueTime: new Date(Date.now() - i * 60000), + source: 'refs/heads/main', + link: '', + }); + } + + return repoBuilds; +}; + +export const Default = () => ( + + + +); + +export const Empty = () => ( + + + +); + +export const Loading = () => ( + + + +); + +export const ErrorMessage = () => ( + + + +); diff --git a/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx b/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx index a308afb81b..f8bb031f0a 100644 --- a/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx +++ b/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx @@ -149,9 +149,7 @@ const columns: TableColumn[] = [ field: 'queueTime', width: 'auto', render: (row: Partial) => - DateTime.fromISO( - row.queueTime ? row.queueTime.toString() : new Date().toString(), - ).toRelative(), + DateTime.fromJSDate(row.queueTime ?? new Date()).toRelative(), }, ]; From 58468331751e52832dff5da225b40b5791588172 Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Tue, 2 Nov 2021 09:28:15 +0100 Subject: [PATCH 36/65] used regex instead of slice Signed-off-by: Alex Rybchenko --- .../src/components/TabbedLayout/RoutedTabs.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core-components/src/components/TabbedLayout/RoutedTabs.tsx b/packages/core-components/src/components/TabbedLayout/RoutedTabs.tsx index c5c771b739..5debafd3f8 100644 --- a/packages/core-components/src/components/TabbedLayout/RoutedTabs.tsx +++ b/packages/core-components/src/components/TabbedLayout/RoutedTabs.tsx @@ -35,8 +35,8 @@ export function useSelectedSubRoute(subRoutes: SubRoute[]): { // TODO: remove once react-router updated const sortedRoutes = routes.sort((a, b) => - // remove added "/*" symbols from path before comparing - b.path.slice(0, -2).localeCompare(a.path.slice(0, -2)), + // remove "/*" symbols from path end before comparing + b.path.replace(/\/\*$/, '').localeCompare(a.path.replace(/\/\*$/, '')), ); const element = useRoutes(sortedRoutes) ?? subRoutes[0].children; From 36350bf8b37f5d2cd8457423cdf1429d7b9015cb Mon Sep 17 00:00:00 2001 From: Jussi Hallila Date: Wed, 3 Nov 2021 15:45:34 +0100 Subject: [PATCH 37/65] Pin version of ElasticSearch client to 7.13.0 Signed-off-by: Jussi Hallila --- .changeset/late-walls-cry.md | 5 +++++ plugins/search-backend-module-elasticsearch/package.json | 2 +- yarn.lock | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/late-walls-cry.md diff --git a/.changeset/late-walls-cry.md b/.changeset/late-walls-cry.md new file mode 100644 index 0000000000..c9fcb3112a --- /dev/null +++ b/.changeset/late-walls-cry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-elasticsearch': patch +--- + +Pinning version of elastic search client to 7.13.0 to prevent breaking change towards third party ElasticSearch clusters on 7.14.0. diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 87688e455f..2d5cabf725 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -22,7 +22,7 @@ "dependencies": { "@backstage/config": "^0.1.8", "@backstage/search-common": "^0.2.0", - "@elastic/elasticsearch": "^7.13.0", + "@elastic/elasticsearch": "7.13.0", "@acuris/aws-es-connection": "^2.2.0", "aws-sdk": "^2.948.0", "elastic-builder": "^2.16.0", diff --git a/yarn.lock b/yarn.lock index 3fec636e32..4faccd29a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2717,7 +2717,7 @@ find-my-way "^2.2.2" into-stream "^5.1.1" -"@elastic/elasticsearch@^7.13.0": +"@elastic/elasticsearch@7.13.0": version "7.13.0" resolved "https://registry.npmjs.org/@elastic/elasticsearch/-/elasticsearch-7.13.0.tgz#6dcf511dfa91187e22c81e54f41f4bd0fd96b4d6" integrity sha512-WgwLWo2p9P2tdqzBGX9fHeG8p5IOTXprXNTECQG2mJ7z9n93N5AFBJpEw4d35tWWeCWi9jI13A2wzQZH7XZ/xw== From bd93a7811b8b5511488f50a0d782005366f055b1 Mon Sep 17 00:00:00 2001 From: Lykke Axlin Date: Thu, 4 Nov 2021 16:48:30 +0100 Subject: [PATCH 38/65] changed @date-io/luxon from 2.x to 1.0 to be compatible with material-ui-pickers Signed-off-by: Lykke Axlin --- plugins/ilert/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json index f33e34beed..f93a5b4870 100644 --- a/plugins/ilert/package.json +++ b/plugins/ilert/package.json @@ -27,7 +27,7 @@ "@backstage/errors": "^0.1.3", "@backstage/plugin-catalog-react": "^0.6.1", "@backstage/theme": "^0.2.12", - "@date-io/luxon": "2.x", + "@date-io/luxon": "1.x", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", From fa325fd89dcbc40f610a70e946107835df073b15 Mon Sep 17 00:00:00 2001 From: Lykke Axlin Date: Thu, 4 Nov 2021 17:06:08 +0100 Subject: [PATCH 39/65] added changeset Signed-off-by: Lykke Axlin --- .changeset/light-knives-camp.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/light-knives-camp.md diff --git a/.changeset/light-knives-camp.md b/.changeset/light-knives-camp.md new file mode 100644 index 0000000000..a68f3ebb9a --- /dev/null +++ b/.changeset/light-knives-camp.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-ilert': patch +--- + +Change the version of @date-io/luxon from 2.x to 1.x to make it compatible with material-ui-pickers From ab27aa313c98987d0d66dd63e0984f1ffebcb149 Mon Sep 17 00:00:00 2001 From: Jeremy Guarini Date: Thu, 4 Nov 2021 09:52:59 -0700 Subject: [PATCH 40/65] Minor UI updates to make numbers and dates a bit more human friendly Signed-off-by: Jeremy Guarini --- .../CoverageHistoryChart/CoverageHistoryChart.tsx | 9 +++++++-- .../src/components/FileExplorer/FileExplorer.tsx | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/plugins/code-coverage/src/components/CoverageHistoryChart/CoverageHistoryChart.tsx b/plugins/code-coverage/src/components/CoverageHistoryChart/CoverageHistoryChart.tsx index 61dc7d7158..a251bb1a68 100644 --- a/plugins/code-coverage/src/components/CoverageHistoryChart/CoverageHistoryChart.tsx +++ b/plugins/code-coverage/src/components/CoverageHistoryChart/CoverageHistoryChart.tsx @@ -69,6 +69,11 @@ const getTrendIcon = (trend: number, classes: ClassNameMap) => { } }; +// convert timestamp to human friendly form +function formatDateToHuman(timeStamp: string | number) { + return new Date(timeStamp).toUTCString(); +} + export const CoverageHistoryChart = () => { const { entity } = useEntity(); const codeCoverageApi = useApi(codeCoverageApiRef); @@ -149,10 +154,10 @@ export const CoverageHistoryChart = () => { margin={{ right: 48, top: 32 }} > - + - + { title: 'Coverage', type: 'numeric', field: 'coverage', - render: (row: CoverageTableRow) => `${row.coverage}%`, + render: (row: CoverageTableRow) => `${row.coverage.toFixed(2)}%`, }, { title: 'Missing lines', From a5512851a0991ec27f3eea530bc223a99013f1ed Mon Sep 17 00:00:00 2001 From: Jeremy Guarini Date: Thu, 4 Nov 2021 10:02:11 -0700 Subject: [PATCH 41/65] add changeset Signed-off-by: Jeremy Guarini --- .changeset/neat-pugs-wait.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/neat-pugs-wait.md diff --git a/.changeset/neat-pugs-wait.md b/.changeset/neat-pugs-wait.md new file mode 100644 index 0000000000..8902f44c9d --- /dev/null +++ b/.changeset/neat-pugs-wait.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-code-coverage': patch +--- + +Change represented test date from epoch to something more human friendly. Round test coverage to 2 decimal places. From 98b8ef555fbeb8d41141360307b21ef7546d6092 Mon Sep 17 00:00:00 2001 From: Matto Date: Fri, 5 Nov 2021 11:03:35 +1100 Subject: [PATCH 42/65] Re-ran prettier on all files Signed-off-by: Matto --- ADOPTERS.md | 128 ++++++++++++++++++++++++++-------------------------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index a2015086c3..797d178270 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -1,64 +1,64 @@ -| Organization | Contact | Description of Use | -| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. | -| [bol.com](https://www.bol.com) | [@sagacity](https://github.com/sagacity) | Initial work being done to unify platform tooling. | -| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | -| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up | -| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | -| [SDA SE](https://sda.se) | [@Fox32](https://github.com/Fox32) | Central place for developing and sharing services in our insurance ecosystem. | -| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. | -| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications | -| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. | -| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | -| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D | -| [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling | -| [Grab](https://www.grab.com) | [@althafh](https://github.com/althafh) | Initial work as a unified interface for all of Grab's internal tooling | -| [Telenor Sweden](https://www.telenor.se) | [@O5ten](https://github.com/O5ten) | Building a developer portal for scaffolding projects towards our unified build environment and microservice stacks | -| [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. | -| [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. | -| [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. | -| [Expedia Group](https://www.expediagroup.com) | [Mike Turner](mailto:miturner@expediagroup.com), [Sneha Kumar](mailto:snkumar@expediagroup.com), [@guillermomanzo](https://github.com/guillermomanzo), [Erik Lindgren](https://github.com/lindgren) | EG Common Developer Toolkit | -| [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go | -| [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling | -| [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. | -| [Trendyol](https://trendyol.com) | [Gamze Senturk](https://github.com/gmzsenturk), [Mert Can Bilgic](https://github.com/mertcb) | The Developer Portal has been called `Pandora`. Provides an overview of Trendyol tech ecosystem. TechDocs, Catalog, Custom Plugins and Theme. | -| [Peloton](https://www.onepeloton.com/) | [Jim Haughwout](https://github.com/JimHaughwout) | Creating our first developer portal and tech-docs. Exploring Service Catalog, Tech Insights and Cost Insights as well. | -| [TELUS](https://telus.com) | [Seb Barre](https://github.com/sbarre) | The Go-to place to find answers about development and delivery at TELUS. | -| [Brex](https://www.brex.com/) | [Vamsi Chitters](https://github.com/vamsikc) | A centralized UI to understand how a service fits in the whole Brex architecture and manage a team’s engineering dependencies. | -| [Oriflame](https://www.oriflame.com/) | [Oriflame](https://github.com/oriflame) | Internal developer portal for services, single page apps and packages overview, API documentation, technical guides, tech-radar and more. | -| [Booz Allen Hamilton](https://www.boozallen.com/) | [Jason Miller](https://github.com/JasonMiller-BAH) | Developer portal for a full-stack software development ecosystem that accelerates consistent and repeatable Modern Software Development practices for internal innovation and investments. | -| [Netflix](https://www.netflix.com/) | [bleathem](https://github.com/bleathem) | Our Backstage implementation will be the front door to a unified experience connecting our internal platform products across important workflows with integrated knowledge and support. | -| [b.well](https://www.icanbwell.com/) | [Jacob Rosales](https://github.com/jrosales) | Foundation for our engineering portal and cloud insights. | -| [PagerDuty](https://www.pagerduty.com/) | [Mark Shaw](https://github.com/markshawtoronto) | Developer portal, initially focused on software templates and tech-docs. | -| [MoonShiner](https://moonshiner.at) | [Fabian Hippmann](https://github.com/FabianHippmann) | Developer portal - helps us keep track of our customer projects, onboard new developers & improve our development process 🌕🚀🧑‍🚀 | -| [FundApps](https://www.fundapps.co/) | [Elliot Greenwood](https://github.com/egnwd) | Developer Portal - A place for us to keep track of our projects and documentation for all services and processes | -| [DAZN](https://dazn.com/) | [Lou Bichard](https://twitter.com/loujaybee), [Marco Crivellaro](https://github.com/crivetechie), [Alex Hollerith](mailto:alex.hollerith@dazn.com) | Ingesting all of DAZN's repos for the catalog, migrating our internal platform apps (pull request boards, release information, inner source marketplace etc) to Backstage plugins (where applicable). | -| [HelloFresh](https://www.hellofresh.de/) | [@iammuho](https://github.com/iammuho), [@ElenaForester](https://github.com/ElenaForester), [@diegomarangoni](https://github.com/diegomarangoni) | Our developer portal at HelloFresh - Spread across an organisation of 500+ engineers globally. | -| [FactSet](https://www.factset.com/) | [@kuangp](https://github.com/kuangp) | Developer portal to provide discoverability to all internal components, APIs, documentation, and scaffold templates with integrations to our internal infrastructure tools. | -| [Workrise](https://www.workrise.com/) | [Michael Rode](https://github.com/michaelrode) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | -| [RedVentures](https://www.redventures.com/) | [Chris Diaz](https://github.com/codingdiaz) | Developer portal that brings everything an engineer needs to provide value into a single pane of glass. | -| [MavTek](https://www.mavtek.com/) | [@fgascon](https://github.com/fgascon) | Developer portal focused on standardizing practices, centralizing documentation and streamlining developer practices. | -| [QuintoAndar](https://www.quintoandar.com.br/) | [@quintoandar](https://github.com/quintoandar) | Developer portal, services catalog and centralization of service metrics. | -| [empathy.co](https://empathy.co/) | [@guillermotti](https://github.com/guillermotti) | Developer portal for tech docs, service catalog, plugin discovery and much more. | -| [creditas.com](https://creditas.com/) | [@aureliosaraiva](https://github.com/aureliosaraiva) [@Creditas](https://github.com/creditas) | Centralization of all services, standards, documentation, etc. We started the deployment process. | -| [Prisjakt](https://www.prisjakt.nu) / [PriceSpy](https://pricespy.co.uk) | [@kennylindahl](https://github.com/kennylindahl) | Internal developer portal - Documentation, scaffolding, software catalog, TechRadar, Gitlab org data integration | -| [Powerspike](https://powerspike.tv/) | [@trelore](https://github.com/trelore) | Developer portal for documentation of core libraries and repositories. | -| [2U](https://2u.com) | [Andrew Thal](https://github.com/athal7) | Development team home-base, promoting service discoverability, resource dependencies, and tech radar | -| [Taxfix](https://taxfix.de/) | [Sami Ur Rehman](https://github.com/samiurrehman92) | Developer's portal with software catalog at it's core. Hosts API Specs, Tech Docs, Tech Radar and some custom plugins. | -| [Busuu](https://busuu.com/) | [Adam Tester](https://github.com/adamtester) | Developer portal with service catalog, API docs, Event docs, service templating, and cost insights. | -| [Loadsmart](https://loadsmart.com/) | [Loadsmart](https://github.com/loadsmart) | Improve services visibility and operations for service owners and developers. | -| [Monzo](https://monzo.com/) | [@WillSewell](https://github.com/WillSewell), [@joechrisellis](https://github.com/joechrisellis) | Developer portal showing metadata and docs for over 2000 microservices. We have built a number of plugins such as a UI for our system to measure [software excellence](https://monzo.com/blog/2021/09/15/how-we-measure-software-excellence), and a UI to show deployment and config change events. | -| [Vaimo](https://www.vaimo.com) | [@vaimo-magnus](https://github.com/vaimo-magnus) | Developer Portal for our developers at Vaimo, currently docs and self-service towards our internal PaaS based on k8s. Plans to extend the catalog into Projects, Environments etc | -| [Wayfair](https://www.wayfair.com) | [@fransan](https://github.com/fransan), [@errskipower](https://github.com/errskipower), [@hrrs](https://github.com/hrrs) | Developer portal for service catalog, technical documentation, and APIs. | -| [CircleHD](https://www.circlehd.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe | -| [CastDesk](https://castdesk.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe | -| [Santagostino](https://santagostino.it) | [@santagostino](https://github.com/santagostino) | Developer portal, gateway to our infrastructure, documentation, service catalog and internal tooling. | -| [Peak](https://peak.ai) | [Luke Beamish](https://github.com/lukebeamish-peak) | Developer portal for all internal engineers to access documentation and tooling. | -| [Gelato](https://gelato.com/) | [Dmitry Makarenko](https://github.com/dmitry-makarenko-gelato) | Developer portal: documentation, service templates, org structure, service catalog, plugins for integration with internal and third-party systems🚀. | -| [GoCardless](https://gocardless.com/) | [James Turley](https://github.com/tragiclifestories) | Developer portal: documentation, service templates, org structure, service catalog, plugins for integration with internal systems. | -| [Box](https://www.box.com) | [@kielosz](https://github.com/kielosz), [@jluk-box](https://github.com/jluk-box), [@ptychu](https://github.com/ptychu), [@alexrybch](https://github.com/alexrybch), [@szubster](https://github.com/szubster) | Developer portal for service catalog, integration with internal systems, new service onboarding. | -| [Bazaarvoice](https://www.bazaarvoice.com) | [@niallmccullagh](https://github.com/niallmccullagh) | Developer portal for service catalog and scaffolds, publishing Github docs and API documentation, visualising our internal tech radar and our product engineering org structure. | -| [Krateo PlatformOps](https://www.krateo.io) | [@projectkerberus](https://github.com/projectkerberus) | A multi-cloud control plane to create, manage and deploy any kind of resource easily and centrally via a Developer Portal that centralizes via a self-service catalog the templating and ownership of services, the available documentation, the overview of the components that compose an entire domain and all the data of the service lifecycle. | -| [Adevinta](https://www.adevinta.com) | [Ray Sinnema](https://github.com/RemonSinnema) | Showcase shared services to our internal customers. | -| [Splunk](https://www.splunk.com) | [@tonytamsf](https://github.com/tonytamsf) | Developer portal as a centralized place to find people, services, documentation, escalation policies and give bravos. This portal is also being used as a centralized search engine for engineering specific documentation.| -| [SoundCloud](https://www.soundcloud.com) | [Julio Zynger](https://github.com/julioz) | Developer portal as a [humane registry](https://martinfowler.com/bliki/HumaneRegistry.html) for the organization: catalog of people, services, documentation, feature toggles, escalation policies, etc.| +| Organization | Contact | Description of Use | +| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. | +| [bol.com](https://www.bol.com) | [@sagacity](https://github.com/sagacity) | Initial work being done to unify platform tooling. | +| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | +| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up | +| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | +| [SDA SE](https://sda.se) | [@Fox32](https://github.com/Fox32) | Central place for developing and sharing services in our insurance ecosystem. | +| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. | +| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications | +| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. | +| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | +| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D | +| [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling | +| [Grab](https://www.grab.com) | [@althafh](https://github.com/althafh) | Initial work as a unified interface for all of Grab's internal tooling | +| [Telenor Sweden](https://www.telenor.se) | [@O5ten](https://github.com/O5ten) | Building a developer portal for scaffolding projects towards our unified build environment and microservice stacks | +| [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. | +| [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. | +| [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. | +| [Expedia Group](https://www.expediagroup.com) | [Mike Turner](mailto:miturner@expediagroup.com), [Sneha Kumar](mailto:snkumar@expediagroup.com), [@guillermomanzo](https://github.com/guillermomanzo), [Erik Lindgren](https://github.com/lindgren) | EG Common Developer Toolkit | +| [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go | +| [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling | +| [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. | +| [Trendyol](https://trendyol.com) | [Gamze Senturk](https://github.com/gmzsenturk), [Mert Can Bilgic](https://github.com/mertcb) | The Developer Portal has been called `Pandora`. Provides an overview of Trendyol tech ecosystem. TechDocs, Catalog, Custom Plugins and Theme. | +| [Peloton](https://www.onepeloton.com/) | [Jim Haughwout](https://github.com/JimHaughwout) | Creating our first developer portal and tech-docs. Exploring Service Catalog, Tech Insights and Cost Insights as well. | +| [TELUS](https://telus.com) | [Seb Barre](https://github.com/sbarre) | The Go-to place to find answers about development and delivery at TELUS. | +| [Brex](https://www.brex.com/) | [Vamsi Chitters](https://github.com/vamsikc) | A centralized UI to understand how a service fits in the whole Brex architecture and manage a team’s engineering dependencies. | +| [Oriflame](https://www.oriflame.com/) | [Oriflame](https://github.com/oriflame) | Internal developer portal for services, single page apps and packages overview, API documentation, technical guides, tech-radar and more. | +| [Booz Allen Hamilton](https://www.boozallen.com/) | [Jason Miller](https://github.com/JasonMiller-BAH) | Developer portal for a full-stack software development ecosystem that accelerates consistent and repeatable Modern Software Development practices for internal innovation and investments. | +| [Netflix](https://www.netflix.com/) | [bleathem](https://github.com/bleathem) | Our Backstage implementation will be the front door to a unified experience connecting our internal platform products across important workflows with integrated knowledge and support. | +| [b.well](https://www.icanbwell.com/) | [Jacob Rosales](https://github.com/jrosales) | Foundation for our engineering portal and cloud insights. | +| [PagerDuty](https://www.pagerduty.com/) | [Mark Shaw](https://github.com/markshawtoronto) | Developer portal, initially focused on software templates and tech-docs. | +| [MoonShiner](https://moonshiner.at) | [Fabian Hippmann](https://github.com/FabianHippmann) | Developer portal - helps us keep track of our customer projects, onboard new developers & improve our development process 🌕🚀🧑‍🚀 | +| [FundApps](https://www.fundapps.co/) | [Elliot Greenwood](https://github.com/egnwd) | Developer Portal - A place for us to keep track of our projects and documentation for all services and processes | +| [DAZN](https://dazn.com/) | [Lou Bichard](https://twitter.com/loujaybee), [Marco Crivellaro](https://github.com/crivetechie), [Alex Hollerith](mailto:alex.hollerith@dazn.com) | Ingesting all of DAZN's repos for the catalog, migrating our internal platform apps (pull request boards, release information, inner source marketplace etc) to Backstage plugins (where applicable). | +| [HelloFresh](https://www.hellofresh.de/) | [@iammuho](https://github.com/iammuho), [@ElenaForester](https://github.com/ElenaForester), [@diegomarangoni](https://github.com/diegomarangoni) | Our developer portal at HelloFresh - Spread across an organisation of 500+ engineers globally. | +| [FactSet](https://www.factset.com/) | [@kuangp](https://github.com/kuangp) | Developer portal to provide discoverability to all internal components, APIs, documentation, and scaffold templates with integrations to our internal infrastructure tools. | +| [Workrise](https://www.workrise.com/) | [Michael Rode](https://github.com/michaelrode) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | +| [RedVentures](https://www.redventures.com/) | [Chris Diaz](https://github.com/codingdiaz) | Developer portal that brings everything an engineer needs to provide value into a single pane of glass. | +| [MavTek](https://www.mavtek.com/) | [@fgascon](https://github.com/fgascon) | Developer portal focused on standardizing practices, centralizing documentation and streamlining developer practices. | +| [QuintoAndar](https://www.quintoandar.com.br/) | [@quintoandar](https://github.com/quintoandar) | Developer portal, services catalog and centralization of service metrics. | +| [empathy.co](https://empathy.co/) | [@guillermotti](https://github.com/guillermotti) | Developer portal for tech docs, service catalog, plugin discovery and much more. | +| [creditas.com](https://creditas.com/) | [@aureliosaraiva](https://github.com/aureliosaraiva) [@Creditas](https://github.com/creditas) | Centralization of all services, standards, documentation, etc. We started the deployment process. | +| [Prisjakt](https://www.prisjakt.nu) / [PriceSpy](https://pricespy.co.uk) | [@kennylindahl](https://github.com/kennylindahl) | Internal developer portal - Documentation, scaffolding, software catalog, TechRadar, Gitlab org data integration | +| [Powerspike](https://powerspike.tv/) | [@trelore](https://github.com/trelore) | Developer portal for documentation of core libraries and repositories. | +| [2U](https://2u.com) | [Andrew Thal](https://github.com/athal7) | Development team home-base, promoting service discoverability, resource dependencies, and tech radar | +| [Taxfix](https://taxfix.de/) | [Sami Ur Rehman](https://github.com/samiurrehman92) | Developer's portal with software catalog at it's core. Hosts API Specs, Tech Docs, Tech Radar and some custom plugins. | +| [Busuu](https://busuu.com/) | [Adam Tester](https://github.com/adamtester) | Developer portal with service catalog, API docs, Event docs, service templating, and cost insights. | +| [Loadsmart](https://loadsmart.com/) | [Loadsmart](https://github.com/loadsmart) | Improve services visibility and operations for service owners and developers. | +| [Monzo](https://monzo.com/) | [@WillSewell](https://github.com/WillSewell), [@joechrisellis](https://github.com/joechrisellis) | Developer portal showing metadata and docs for over 2000 microservices. We have built a number of plugins such as a UI for our system to measure [software excellence](https://monzo.com/blog/2021/09/15/how-we-measure-software-excellence), and a UI to show deployment and config change events. | +| [Vaimo](https://www.vaimo.com) | [@vaimo-magnus](https://github.com/vaimo-magnus) | Developer Portal for our developers at Vaimo, currently docs and self-service towards our internal PaaS based on k8s. Plans to extend the catalog into Projects, Environments etc | +| [Wayfair](https://www.wayfair.com) | [@fransan](https://github.com/fransan), [@errskipower](https://github.com/errskipower), [@hrrs](https://github.com/hrrs) | Developer portal for service catalog, technical documentation, and APIs. | +| [CircleHD](https://www.circlehd.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe | +| [CastDesk](https://castdesk.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe | +| [Santagostino](https://santagostino.it) | [@santagostino](https://github.com/santagostino) | Developer portal, gateway to our infrastructure, documentation, service catalog and internal tooling. | +| [Peak](https://peak.ai) | [Luke Beamish](https://github.com/lukebeamish-peak) | Developer portal for all internal engineers to access documentation and tooling. | +| [Gelato](https://gelato.com/) | [Dmitry Makarenko](https://github.com/dmitry-makarenko-gelato) | Developer portal: documentation, service templates, org structure, service catalog, plugins for integration with internal and third-party systems🚀. | +| [GoCardless](https://gocardless.com/) | [James Turley](https://github.com/tragiclifestories) | Developer portal: documentation, service templates, org structure, service catalog, plugins for integration with internal systems. | +| [Box](https://www.box.com) | [@kielosz](https://github.com/kielosz), [@jluk-box](https://github.com/jluk-box), [@ptychu](https://github.com/ptychu), [@alexrybch](https://github.com/alexrybch), [@szubster](https://github.com/szubster) | Developer portal for service catalog, integration with internal systems, new service onboarding. | +| [Bazaarvoice](https://www.bazaarvoice.com) | [@niallmccullagh](https://github.com/niallmccullagh) | Developer portal for service catalog and scaffolds, publishing Github docs and API documentation, visualising our internal tech radar and our product engineering org structure. | +| [Krateo PlatformOps](https://www.krateo.io) | [@projectkerberus](https://github.com/projectkerberus) | A multi-cloud control plane to create, manage and deploy any kind of resource easily and centrally via a Developer Portal that centralizes via a self-service catalog the templating and ownership of services, the available documentation, the overview of the components that compose an entire domain and all the data of the service lifecycle. | +| [Adevinta](https://www.adevinta.com) | [Ray Sinnema](https://github.com/RemonSinnema) | Showcase shared services to our internal customers. | +| [Splunk](https://www.splunk.com) | [@tonytamsf](https://github.com/tonytamsf) | Developer portal as a centralized place to find people, services, documentation, escalation policies and give bravos. This portal is also being used as a centralized search engine for engineering specific documentation. | +| [SoundCloud](https://www.soundcloud.com) | [Julio Zynger](https://github.com/julioz) | Developer portal as a [humane registry](https://martinfowler.com/bliki/HumaneRegistry.html) for the organization: catalog of people, services, documentation, feature toggles, escalation policies, etc. | From e37a84085624703018b48746fad5337883317a97 Mon Sep 17 00:00:00 2001 From: Matto Date: Fri, 5 Nov 2021 11:17:01 +1100 Subject: [PATCH 43/65] Re-ran prettier on all files Signed-off-by: Matto --- ADOPTERS.md | 128 +++++++++++++------------- packages/backend-common/src/config.ts | 2 +- 2 files changed, 65 insertions(+), 65 deletions(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index a2015086c3..797d178270 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -1,64 +1,64 @@ -| Organization | Contact | Description of Use | -| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. | -| [bol.com](https://www.bol.com) | [@sagacity](https://github.com/sagacity) | Initial work being done to unify platform tooling. | -| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | -| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up | -| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | -| [SDA SE](https://sda.se) | [@Fox32](https://github.com/Fox32) | Central place for developing and sharing services in our insurance ecosystem. | -| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. | -| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications | -| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. | -| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | -| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D | -| [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling | -| [Grab](https://www.grab.com) | [@althafh](https://github.com/althafh) | Initial work as a unified interface for all of Grab's internal tooling | -| [Telenor Sweden](https://www.telenor.se) | [@O5ten](https://github.com/O5ten) | Building a developer portal for scaffolding projects towards our unified build environment and microservice stacks | -| [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. | -| [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. | -| [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. | -| [Expedia Group](https://www.expediagroup.com) | [Mike Turner](mailto:miturner@expediagroup.com), [Sneha Kumar](mailto:snkumar@expediagroup.com), [@guillermomanzo](https://github.com/guillermomanzo), [Erik Lindgren](https://github.com/lindgren) | EG Common Developer Toolkit | -| [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go | -| [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling | -| [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. | -| [Trendyol](https://trendyol.com) | [Gamze Senturk](https://github.com/gmzsenturk), [Mert Can Bilgic](https://github.com/mertcb) | The Developer Portal has been called `Pandora`. Provides an overview of Trendyol tech ecosystem. TechDocs, Catalog, Custom Plugins and Theme. | -| [Peloton](https://www.onepeloton.com/) | [Jim Haughwout](https://github.com/JimHaughwout) | Creating our first developer portal and tech-docs. Exploring Service Catalog, Tech Insights and Cost Insights as well. | -| [TELUS](https://telus.com) | [Seb Barre](https://github.com/sbarre) | The Go-to place to find answers about development and delivery at TELUS. | -| [Brex](https://www.brex.com/) | [Vamsi Chitters](https://github.com/vamsikc) | A centralized UI to understand how a service fits in the whole Brex architecture and manage a team’s engineering dependencies. | -| [Oriflame](https://www.oriflame.com/) | [Oriflame](https://github.com/oriflame) | Internal developer portal for services, single page apps and packages overview, API documentation, technical guides, tech-radar and more. | -| [Booz Allen Hamilton](https://www.boozallen.com/) | [Jason Miller](https://github.com/JasonMiller-BAH) | Developer portal for a full-stack software development ecosystem that accelerates consistent and repeatable Modern Software Development practices for internal innovation and investments. | -| [Netflix](https://www.netflix.com/) | [bleathem](https://github.com/bleathem) | Our Backstage implementation will be the front door to a unified experience connecting our internal platform products across important workflows with integrated knowledge and support. | -| [b.well](https://www.icanbwell.com/) | [Jacob Rosales](https://github.com/jrosales) | Foundation for our engineering portal and cloud insights. | -| [PagerDuty](https://www.pagerduty.com/) | [Mark Shaw](https://github.com/markshawtoronto) | Developer portal, initially focused on software templates and tech-docs. | -| [MoonShiner](https://moonshiner.at) | [Fabian Hippmann](https://github.com/FabianHippmann) | Developer portal - helps us keep track of our customer projects, onboard new developers & improve our development process 🌕🚀🧑‍🚀 | -| [FundApps](https://www.fundapps.co/) | [Elliot Greenwood](https://github.com/egnwd) | Developer Portal - A place for us to keep track of our projects and documentation for all services and processes | -| [DAZN](https://dazn.com/) | [Lou Bichard](https://twitter.com/loujaybee), [Marco Crivellaro](https://github.com/crivetechie), [Alex Hollerith](mailto:alex.hollerith@dazn.com) | Ingesting all of DAZN's repos for the catalog, migrating our internal platform apps (pull request boards, release information, inner source marketplace etc) to Backstage plugins (where applicable). | -| [HelloFresh](https://www.hellofresh.de/) | [@iammuho](https://github.com/iammuho), [@ElenaForester](https://github.com/ElenaForester), [@diegomarangoni](https://github.com/diegomarangoni) | Our developer portal at HelloFresh - Spread across an organisation of 500+ engineers globally. | -| [FactSet](https://www.factset.com/) | [@kuangp](https://github.com/kuangp) | Developer portal to provide discoverability to all internal components, APIs, documentation, and scaffold templates with integrations to our internal infrastructure tools. | -| [Workrise](https://www.workrise.com/) | [Michael Rode](https://github.com/michaelrode) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | -| [RedVentures](https://www.redventures.com/) | [Chris Diaz](https://github.com/codingdiaz) | Developer portal that brings everything an engineer needs to provide value into a single pane of glass. | -| [MavTek](https://www.mavtek.com/) | [@fgascon](https://github.com/fgascon) | Developer portal focused on standardizing practices, centralizing documentation and streamlining developer practices. | -| [QuintoAndar](https://www.quintoandar.com.br/) | [@quintoandar](https://github.com/quintoandar) | Developer portal, services catalog and centralization of service metrics. | -| [empathy.co](https://empathy.co/) | [@guillermotti](https://github.com/guillermotti) | Developer portal for tech docs, service catalog, plugin discovery and much more. | -| [creditas.com](https://creditas.com/) | [@aureliosaraiva](https://github.com/aureliosaraiva) [@Creditas](https://github.com/creditas) | Centralization of all services, standards, documentation, etc. We started the deployment process. | -| [Prisjakt](https://www.prisjakt.nu) / [PriceSpy](https://pricespy.co.uk) | [@kennylindahl](https://github.com/kennylindahl) | Internal developer portal - Documentation, scaffolding, software catalog, TechRadar, Gitlab org data integration | -| [Powerspike](https://powerspike.tv/) | [@trelore](https://github.com/trelore) | Developer portal for documentation of core libraries and repositories. | -| [2U](https://2u.com) | [Andrew Thal](https://github.com/athal7) | Development team home-base, promoting service discoverability, resource dependencies, and tech radar | -| [Taxfix](https://taxfix.de/) | [Sami Ur Rehman](https://github.com/samiurrehman92) | Developer's portal with software catalog at it's core. Hosts API Specs, Tech Docs, Tech Radar and some custom plugins. | -| [Busuu](https://busuu.com/) | [Adam Tester](https://github.com/adamtester) | Developer portal with service catalog, API docs, Event docs, service templating, and cost insights. | -| [Loadsmart](https://loadsmart.com/) | [Loadsmart](https://github.com/loadsmart) | Improve services visibility and operations for service owners and developers. | -| [Monzo](https://monzo.com/) | [@WillSewell](https://github.com/WillSewell), [@joechrisellis](https://github.com/joechrisellis) | Developer portal showing metadata and docs for over 2000 microservices. We have built a number of plugins such as a UI for our system to measure [software excellence](https://monzo.com/blog/2021/09/15/how-we-measure-software-excellence), and a UI to show deployment and config change events. | -| [Vaimo](https://www.vaimo.com) | [@vaimo-magnus](https://github.com/vaimo-magnus) | Developer Portal for our developers at Vaimo, currently docs and self-service towards our internal PaaS based on k8s. Plans to extend the catalog into Projects, Environments etc | -| [Wayfair](https://www.wayfair.com) | [@fransan](https://github.com/fransan), [@errskipower](https://github.com/errskipower), [@hrrs](https://github.com/hrrs) | Developer portal for service catalog, technical documentation, and APIs. | -| [CircleHD](https://www.circlehd.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe | -| [CastDesk](https://castdesk.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe | -| [Santagostino](https://santagostino.it) | [@santagostino](https://github.com/santagostino) | Developer portal, gateway to our infrastructure, documentation, service catalog and internal tooling. | -| [Peak](https://peak.ai) | [Luke Beamish](https://github.com/lukebeamish-peak) | Developer portal for all internal engineers to access documentation and tooling. | -| [Gelato](https://gelato.com/) | [Dmitry Makarenko](https://github.com/dmitry-makarenko-gelato) | Developer portal: documentation, service templates, org structure, service catalog, plugins for integration with internal and third-party systems🚀. | -| [GoCardless](https://gocardless.com/) | [James Turley](https://github.com/tragiclifestories) | Developer portal: documentation, service templates, org structure, service catalog, plugins for integration with internal systems. | -| [Box](https://www.box.com) | [@kielosz](https://github.com/kielosz), [@jluk-box](https://github.com/jluk-box), [@ptychu](https://github.com/ptychu), [@alexrybch](https://github.com/alexrybch), [@szubster](https://github.com/szubster) | Developer portal for service catalog, integration with internal systems, new service onboarding. | -| [Bazaarvoice](https://www.bazaarvoice.com) | [@niallmccullagh](https://github.com/niallmccullagh) | Developer portal for service catalog and scaffolds, publishing Github docs and API documentation, visualising our internal tech radar and our product engineering org structure. | -| [Krateo PlatformOps](https://www.krateo.io) | [@projectkerberus](https://github.com/projectkerberus) | A multi-cloud control plane to create, manage and deploy any kind of resource easily and centrally via a Developer Portal that centralizes via a self-service catalog the templating and ownership of services, the available documentation, the overview of the components that compose an entire domain and all the data of the service lifecycle. | -| [Adevinta](https://www.adevinta.com) | [Ray Sinnema](https://github.com/RemonSinnema) | Showcase shared services to our internal customers. | -| [Splunk](https://www.splunk.com) | [@tonytamsf](https://github.com/tonytamsf) | Developer portal as a centralized place to find people, services, documentation, escalation policies and give bravos. This portal is also being used as a centralized search engine for engineering specific documentation.| -| [SoundCloud](https://www.soundcloud.com) | [Julio Zynger](https://github.com/julioz) | Developer portal as a [humane registry](https://martinfowler.com/bliki/HumaneRegistry.html) for the organization: catalog of people, services, documentation, feature toggles, escalation policies, etc.| +| Organization | Contact | Description of Use | +| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. | +| [bol.com](https://www.bol.com) | [@sagacity](https://github.com/sagacity) | Initial work being done to unify platform tooling. | +| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | +| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up | +| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | +| [SDA SE](https://sda.se) | [@Fox32](https://github.com/Fox32) | Central place for developing and sharing services in our insurance ecosystem. | +| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. | +| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications | +| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. | +| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | +| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D | +| [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling | +| [Grab](https://www.grab.com) | [@althafh](https://github.com/althafh) | Initial work as a unified interface for all of Grab's internal tooling | +| [Telenor Sweden](https://www.telenor.se) | [@O5ten](https://github.com/O5ten) | Building a developer portal for scaffolding projects towards our unified build environment and microservice stacks | +| [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. | +| [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. | +| [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. | +| [Expedia Group](https://www.expediagroup.com) | [Mike Turner](mailto:miturner@expediagroup.com), [Sneha Kumar](mailto:snkumar@expediagroup.com), [@guillermomanzo](https://github.com/guillermomanzo), [Erik Lindgren](https://github.com/lindgren) | EG Common Developer Toolkit | +| [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go | +| [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling | +| [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. | +| [Trendyol](https://trendyol.com) | [Gamze Senturk](https://github.com/gmzsenturk), [Mert Can Bilgic](https://github.com/mertcb) | The Developer Portal has been called `Pandora`. Provides an overview of Trendyol tech ecosystem. TechDocs, Catalog, Custom Plugins and Theme. | +| [Peloton](https://www.onepeloton.com/) | [Jim Haughwout](https://github.com/JimHaughwout) | Creating our first developer portal and tech-docs. Exploring Service Catalog, Tech Insights and Cost Insights as well. | +| [TELUS](https://telus.com) | [Seb Barre](https://github.com/sbarre) | The Go-to place to find answers about development and delivery at TELUS. | +| [Brex](https://www.brex.com/) | [Vamsi Chitters](https://github.com/vamsikc) | A centralized UI to understand how a service fits in the whole Brex architecture and manage a team’s engineering dependencies. | +| [Oriflame](https://www.oriflame.com/) | [Oriflame](https://github.com/oriflame) | Internal developer portal for services, single page apps and packages overview, API documentation, technical guides, tech-radar and more. | +| [Booz Allen Hamilton](https://www.boozallen.com/) | [Jason Miller](https://github.com/JasonMiller-BAH) | Developer portal for a full-stack software development ecosystem that accelerates consistent and repeatable Modern Software Development practices for internal innovation and investments. | +| [Netflix](https://www.netflix.com/) | [bleathem](https://github.com/bleathem) | Our Backstage implementation will be the front door to a unified experience connecting our internal platform products across important workflows with integrated knowledge and support. | +| [b.well](https://www.icanbwell.com/) | [Jacob Rosales](https://github.com/jrosales) | Foundation for our engineering portal and cloud insights. | +| [PagerDuty](https://www.pagerduty.com/) | [Mark Shaw](https://github.com/markshawtoronto) | Developer portal, initially focused on software templates and tech-docs. | +| [MoonShiner](https://moonshiner.at) | [Fabian Hippmann](https://github.com/FabianHippmann) | Developer portal - helps us keep track of our customer projects, onboard new developers & improve our development process 🌕🚀🧑‍🚀 | +| [FundApps](https://www.fundapps.co/) | [Elliot Greenwood](https://github.com/egnwd) | Developer Portal - A place for us to keep track of our projects and documentation for all services and processes | +| [DAZN](https://dazn.com/) | [Lou Bichard](https://twitter.com/loujaybee), [Marco Crivellaro](https://github.com/crivetechie), [Alex Hollerith](mailto:alex.hollerith@dazn.com) | Ingesting all of DAZN's repos for the catalog, migrating our internal platform apps (pull request boards, release information, inner source marketplace etc) to Backstage plugins (where applicable). | +| [HelloFresh](https://www.hellofresh.de/) | [@iammuho](https://github.com/iammuho), [@ElenaForester](https://github.com/ElenaForester), [@diegomarangoni](https://github.com/diegomarangoni) | Our developer portal at HelloFresh - Spread across an organisation of 500+ engineers globally. | +| [FactSet](https://www.factset.com/) | [@kuangp](https://github.com/kuangp) | Developer portal to provide discoverability to all internal components, APIs, documentation, and scaffold templates with integrations to our internal infrastructure tools. | +| [Workrise](https://www.workrise.com/) | [Michael Rode](https://github.com/michaelrode) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | +| [RedVentures](https://www.redventures.com/) | [Chris Diaz](https://github.com/codingdiaz) | Developer portal that brings everything an engineer needs to provide value into a single pane of glass. | +| [MavTek](https://www.mavtek.com/) | [@fgascon](https://github.com/fgascon) | Developer portal focused on standardizing practices, centralizing documentation and streamlining developer practices. | +| [QuintoAndar](https://www.quintoandar.com.br/) | [@quintoandar](https://github.com/quintoandar) | Developer portal, services catalog and centralization of service metrics. | +| [empathy.co](https://empathy.co/) | [@guillermotti](https://github.com/guillermotti) | Developer portal for tech docs, service catalog, plugin discovery and much more. | +| [creditas.com](https://creditas.com/) | [@aureliosaraiva](https://github.com/aureliosaraiva) [@Creditas](https://github.com/creditas) | Centralization of all services, standards, documentation, etc. We started the deployment process. | +| [Prisjakt](https://www.prisjakt.nu) / [PriceSpy](https://pricespy.co.uk) | [@kennylindahl](https://github.com/kennylindahl) | Internal developer portal - Documentation, scaffolding, software catalog, TechRadar, Gitlab org data integration | +| [Powerspike](https://powerspike.tv/) | [@trelore](https://github.com/trelore) | Developer portal for documentation of core libraries and repositories. | +| [2U](https://2u.com) | [Andrew Thal](https://github.com/athal7) | Development team home-base, promoting service discoverability, resource dependencies, and tech radar | +| [Taxfix](https://taxfix.de/) | [Sami Ur Rehman](https://github.com/samiurrehman92) | Developer's portal with software catalog at it's core. Hosts API Specs, Tech Docs, Tech Radar and some custom plugins. | +| [Busuu](https://busuu.com/) | [Adam Tester](https://github.com/adamtester) | Developer portal with service catalog, API docs, Event docs, service templating, and cost insights. | +| [Loadsmart](https://loadsmart.com/) | [Loadsmart](https://github.com/loadsmart) | Improve services visibility and operations for service owners and developers. | +| [Monzo](https://monzo.com/) | [@WillSewell](https://github.com/WillSewell), [@joechrisellis](https://github.com/joechrisellis) | Developer portal showing metadata and docs for over 2000 microservices. We have built a number of plugins such as a UI for our system to measure [software excellence](https://monzo.com/blog/2021/09/15/how-we-measure-software-excellence), and a UI to show deployment and config change events. | +| [Vaimo](https://www.vaimo.com) | [@vaimo-magnus](https://github.com/vaimo-magnus) | Developer Portal for our developers at Vaimo, currently docs and self-service towards our internal PaaS based on k8s. Plans to extend the catalog into Projects, Environments etc | +| [Wayfair](https://www.wayfair.com) | [@fransan](https://github.com/fransan), [@errskipower](https://github.com/errskipower), [@hrrs](https://github.com/hrrs) | Developer portal for service catalog, technical documentation, and APIs. | +| [CircleHD](https://www.circlehd.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe | +| [CastDesk](https://castdesk.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe | +| [Santagostino](https://santagostino.it) | [@santagostino](https://github.com/santagostino) | Developer portal, gateway to our infrastructure, documentation, service catalog and internal tooling. | +| [Peak](https://peak.ai) | [Luke Beamish](https://github.com/lukebeamish-peak) | Developer portal for all internal engineers to access documentation and tooling. | +| [Gelato](https://gelato.com/) | [Dmitry Makarenko](https://github.com/dmitry-makarenko-gelato) | Developer portal: documentation, service templates, org structure, service catalog, plugins for integration with internal and third-party systems🚀. | +| [GoCardless](https://gocardless.com/) | [James Turley](https://github.com/tragiclifestories) | Developer portal: documentation, service templates, org structure, service catalog, plugins for integration with internal systems. | +| [Box](https://www.box.com) | [@kielosz](https://github.com/kielosz), [@jluk-box](https://github.com/jluk-box), [@ptychu](https://github.com/ptychu), [@alexrybch](https://github.com/alexrybch), [@szubster](https://github.com/szubster) | Developer portal for service catalog, integration with internal systems, new service onboarding. | +| [Bazaarvoice](https://www.bazaarvoice.com) | [@niallmccullagh](https://github.com/niallmccullagh) | Developer portal for service catalog and scaffolds, publishing Github docs and API documentation, visualising our internal tech radar and our product engineering org structure. | +| [Krateo PlatformOps](https://www.krateo.io) | [@projectkerberus](https://github.com/projectkerberus) | A multi-cloud control plane to create, manage and deploy any kind of resource easily and centrally via a Developer Portal that centralizes via a self-service catalog the templating and ownership of services, the available documentation, the overview of the components that compose an entire domain and all the data of the service lifecycle. | +| [Adevinta](https://www.adevinta.com) | [Ray Sinnema](https://github.com/RemonSinnema) | Showcase shared services to our internal customers. | +| [Splunk](https://www.splunk.com) | [@tonytamsf](https://github.com/tonytamsf) | Developer portal as a centralized place to find people, services, documentation, escalation policies and give bravos. This portal is also being used as a centralized search engine for engineering specific documentation. | +| [SoundCloud](https://www.soundcloud.com) | [Julio Zynger](https://github.com/julioz) | Developer portal as a [humane registry](https://martinfowler.com/bliki/HumaneRegistry.html) for the organization: catalog of people, services, documentation, feature toggles, escalation policies, etc. | diff --git a/packages/backend-common/src/config.ts b/packages/backend-common/src/config.ts index 3c5a2d7b26..3fd5e1b44e 100644 --- a/packages/backend-common/src/config.ts +++ b/packages/backend-common/src/config.ts @@ -22,7 +22,7 @@ import { loadConfigSchema, loadConfig, ConfigSchema, - ConfigTarget + ConfigTarget, } from '@backstage/config-loader'; import { AppConfig, Config, ConfigReader } from '@backstage/config'; import { JsonValue } from '@backstage/types'; From 89e9f486f1f925684741d0f81391f73c5c89977e Mon Sep 17 00:00:00 2001 From: Matto Date: Fri, 5 Nov 2021 12:45:14 +1100 Subject: [PATCH 44/65] api-report fix Signed-off-by: Matto --- packages/config-loader/api-report.md | 30 ++++++++++++++++------------ 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/config-loader/api-report.md b/packages/config-loader/api-report.md index be4678aa0f..6f568df942 100644 --- a/packages/config-loader/api-report.md +++ b/packages/config-loader/api-report.md @@ -51,6 +51,21 @@ export type LoadConfigOptions = { watch?: LoadConfigOptionsWatch; }; +// Warning: (ae-missing-release-tag) "LoadConfigOptionsRemote" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type LoadConfigOptionsRemote = { + reloadIntervalSeconds: number; +}; + +// Warning: (ae-missing-release-tag) "LoadConfigOptionsWatch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type LoadConfigOptionsWatch = { + onChange: (configs: AppConfig[]) => void; + stopSignal?: Promise; +}; + // @public export function loadConfigSchema( options: LoadConfigSchemaOptions, @@ -74,13 +89,6 @@ export function readEnvConfig(env: { [name: string]: string | undefined; }): AppConfig[]; -// Warning: (ae-missing-release-tag) "LoadConfigOptionsRemote" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type LoadConfigOptionsRemote = { - reloadIntervalSeconds: number; -}; - // @public export type TransformFunc = ( value: T, @@ -89,11 +97,7 @@ export type TransformFunc = ( }, ) => T | undefined; -// Warning: (ae-missing-release-tag) "LoadConfigOptionsWatch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warnings were encountered during analysis: // -// @public (undocumented) -export type LoadConfigOptionsWatch = { - onChange: (configs: AppConfig[]) => void; - stopSignal?: Promise; -}; +// src/loader.d.ts:33:5 - (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/config-loader" does not have an export "configTargets" ``` From 42e97f470890daa4cefda0fc239a9c3084d0f1b9 Mon Sep 17 00:00:00 2001 From: Lykke Axlin Date: Fri, 5 Nov 2021 08:09:36 +0100 Subject: [PATCH 45/65] update yarn.lock and fixed typo Signed-off-by: Lykke Axlin --- .changeset/light-knives-camp.md | 2 +- yarn.lock | 15 +++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.changeset/light-knives-camp.md b/.changeset/light-knives-camp.md index a68f3ebb9a..1e7fa37082 100644 --- a/.changeset/light-knives-camp.md +++ b/.changeset/light-knives-camp.md @@ -2,4 +2,4 @@ '@backstage/plugin-ilert': patch --- -Change the version of @date-io/luxon from 2.x to 1.x to make it compatible with material-ui-pickers +Change the version of `@date-io/luxon` from 2.x to 1.x to make it compatible with material-ui-pickers diff --git a/yarn.lock b/yarn.lock index 3fec636e32..67e23eb89d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2684,11 +2684,6 @@ resolved "https://registry.npmjs.org/@date-io/core/-/core-2.10.7.tgz#0fe1fa0ef02c827919e23c2802a4b25589ac522d" integrity sha512-EG/1qDiQvd12RoNJ6H+sZcHVswC/3uMx/ySvfaJ24vB30rLjkgHggEXbgMbfgki7wMuiQ/zXI8QlmF1k3kWRGQ== -"@date-io/core@^2.10.11": - version "2.10.11" - resolved "https://registry.npmjs.org/@date-io/core/-/core-2.10.11.tgz#b1a3d57730f3eaaab54d5658be4a71727297e357" - integrity sha512-keXQnwH0LM8wyvu+j5Z2KGK56D+eItjy7DnwuWl/oV+DM2UEYl0z5WhdPMpfswSyt/kjuPOzcVF/7u/skMLaoA== - "@date-io/date-fns@^1.3.13": version "1.3.13" resolved "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-1.3.13.tgz#7798844041640ab393f7e21a7769a65d672f4735" @@ -2696,12 +2691,12 @@ dependencies: "@date-io/core" "^1.3.13" -"@date-io/luxon@2.x": - version "2.10.11" - resolved "https://registry.npmjs.org/@date-io/luxon/-/luxon-2.10.11.tgz#d0981b9fdf5e5f17f8ce59265a3ac6c335565fac" - integrity sha512-SS6SIkp0Y9GFwpQycCTUAyW3OZTW05CWI1DJu10hUzcg8SmjJfhjs7hQY3TOeW+JT6VtXGTVGwbWPUBJsNkhZg== +"@date-io/luxon@1.x": + version "1.3.13" + resolved "https://registry.npmjs.org/@date-io/luxon/-/luxon-1.3.13.tgz#68f0134bb38ef486b2ed6df01981f814c633e28a" + integrity sha512-9wUrJCNSMZJeYAiH+dbb45oGpnHeFP7TOH/Lt26If47gjFCkjvyINzWx+K5AGsnlP0Qosxc7hkF1yLi6ecutxw== dependencies: - "@date-io/core" "^2.10.11" + "@date-io/core" "^1.3.13" "@discoveryjs/json-ext@^0.5.3": version "0.5.5" From ec64d9590cbb4dad0f8832da7f41e7a02e8115bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sat, 6 Nov 2021 10:25:17 +0100 Subject: [PATCH 46/65] Call the super constructor early MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/nice-tools-cheat.md | 5 +++++ packages/cli/src/lib/errors.ts | 10 +++++----- 2 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 .changeset/nice-tools-cheat.md diff --git a/.changeset/nice-tools-cheat.md b/.changeset/nice-tools-cheat.md new file mode 100644 index 0000000000..c4f0e162bf --- /dev/null +++ b/.changeset/nice-tools-cheat.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Make `ExitCodeError` call `super` early to avoid compiler warnings diff --git a/packages/cli/src/lib/errors.ts b/packages/cli/src/lib/errors.ts index c0b4c45b6c..dd3955a10a 100644 --- a/packages/cli/src/lib/errors.ts +++ b/packages/cli/src/lib/errors.ts @@ -26,11 +26,11 @@ export class ExitCodeError extends CustomError { readonly code: number; constructor(code: number, command?: string) { - if (command) { - super(`Command '${command}' exited with code ${code}`); - } else { - super(`Child exited with code ${code}`); - } + super( + command + ? `Command '${command}' exited with code ${code}` + : `Child exited with code ${code}`, + ); this.code = code; } } From e7e4626fc9308f63375cd8b07b9196fd57d14079 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 8 Nov 2021 10:38:42 +0100 Subject: [PATCH 47/65] chore: remove warning Signed-off-by: blam --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 13b7c945cf..1078631926 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ # [Backstage](https://backstage.io) -> 🏖 All of the maintainers will be taking a wellness break Nov. 1–5. The repo and Discord may be quieter than usual, but not to worry. We’ll have coverage plans in place and be back in full force, rested and restored, on Nov. 8. 🏖 - [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![CNCF Status](https://img.shields.io/badge/cncf%20status-sandbox-blue.svg)](https://www.cncf.io/projects) [![Main CI Build](https://github.com/backstage/backstage/workflows/Main%20Master%20Build/badge.svg)](https://github.com/backstage/backstage/actions?query=workflow%3A%22Main+Master+Build%22) From 064297ea19eeecb12e8a54ac6d8d503bcd85379a Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 8 Nov 2021 10:57:58 +0100 Subject: [PATCH 48/65] chore: adding some documentation for exported things Signed-off-by: blam --- .../src/hooks/useEntityOwnership.ts | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/plugins/catalog-react/src/hooks/useEntityOwnership.ts b/plugins/catalog-react/src/hooks/useEntityOwnership.ts index aee903603a..54e9e0ff99 100644 --- a/plugins/catalog-react/src/hooks/useEntityOwnership.ts +++ b/plugins/catalog-react/src/hooks/useEntityOwnership.ts @@ -49,9 +49,16 @@ function extendUserId(id: string): string { } } -// Takes the relevant parts of the Backstage identity, and translates them into -// a list of entity refs on string form that represent the user's ownership -// connections. +/** + * Takes the relevant parts of the Backstage identity, and translates them into + * a list of entity refs on string form that represent the user's ownership + * connections. + * + * @public + * + * @param identityApi - The IdentityApi implementation + * @returns IdentityOwner refs as a string array + */ export async function loadIdentityOwnerRefs( identityApi: IdentityApi, ): Promise { @@ -81,9 +88,17 @@ export async function loadIdentityOwnerRefs( return result; } -// Takes the relevant parts of the User entity corresponding to the Backstage -// identity, and translates them into a list of entity refs on string form that -// represent the user's ownership connections. +/** + * Takes the relevant parts of the User entity corresponding to the Backstage + * identity, and translates them into a list of entity refs on string form that + * represent the user's ownership connections. + * + * @public + * + * @param catalogApi - The Catalog API implementation + * @param identityOwnerRefs - List of identity owner refs as strings + * @returns OwnerRefs as a string array + */ export async function loadCatalogOwnerRefs( catalogApi: CatalogApi, identityOwnerRefs: string[], @@ -113,6 +128,10 @@ export async function loadCatalogOwnerRefs( * owner of a given entity. When the hook is initially mounted, the loading * flag will be true and the results returned from the function will always be * false. + * + * @public + * + * @returns a function that checks if the signed in user owns an entity */ export function useEntityOwnership(): { loading: boolean; From 0aec087ee6422bc896b9c33c4b6a9cff6b26e9e9 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 8 Nov 2021 11:00:30 +0100 Subject: [PATCH 49/65] chore: updating API report Signed-off-by: blam --- plugins/catalog-react/api-report.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index 07a924bfa3..19a4dfd930 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -727,17 +727,13 @@ export function getEntitySourceLocation( // @public export function isOwnerOf(owner: Entity, owned: Entity): boolean; -// Warning: (ae-missing-release-tag) "loadCatalogOwnerRefs" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function loadCatalogOwnerRefs( catalogApi: CatalogApi, identityOwnerRefs: string[], ): Promise; -// Warning: (ae-missing-release-tag) "loadIdentityOwnerRefs" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function loadIdentityOwnerRefs( identityApi: IdentityApi, ): Promise; @@ -829,8 +825,6 @@ export function useEntityListProvider< EntityFilters extends DefaultEntityFilters = DefaultEntityFilters, >(): EntityListContextProps; -// Warning: (ae-missing-release-tag) "useEntityOwnership" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export function useEntityOwnership(): { loading: boolean; From 44308cc80daeafd9ae5debc148d724f1e06cbeae Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 8 Nov 2021 11:40:31 +0100 Subject: [PATCH 50/65] chore: remove some duplicate dependencies Signed-off-by: blam --- yarn.lock | 119 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 93 insertions(+), 26 deletions(-) diff --git a/yarn.lock b/yarn.lock index e879cff365..905d2e7bef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5449,18 +5449,18 @@ integrity sha512-dOI13j1uHMZkRxhZuge/ugOE7Aqcg7Nxki932lDZuXyY4G8CGxkc/66PeQ8pR4PCzThHORXo7Ptvau6bh101lQ== "@storybook/addon-a11y@^6.3.4": - version "6.3.7" - resolved "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-6.3.7.tgz#a802455f2d932eda07314e3d44a96c94bbd22b3d" - integrity sha512-Z5Lhxm8r5CkPW9FYf6zmAk9c7IhUeUQZxKZeEWGZdOvcjQ32rtg4IYvO2SHgWNrEKBdxxFm3pMiyK3wylQLfsQ== + version "6.3.12" + resolved "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-6.3.12.tgz#2f930fc84fc275a4ed43a716fc09cc12caf4e110" + integrity sha512-q1NdRHFJV6sLEEJw0hatCc5ZIthELqM/AWdrEWDyhcJNyiq7Tq4nKqQBMTQSYwHiUAmxVgw7i4oa1vM2M51/3g== dependencies: - "@storybook/addons" "6.3.7" - "@storybook/api" "6.3.7" - "@storybook/channels" "6.3.7" - "@storybook/client-api" "6.3.7" - "@storybook/client-logger" "6.3.7" - "@storybook/components" "6.3.7" - "@storybook/core-events" "6.3.7" - "@storybook/theming" "6.3.7" + "@storybook/addons" "6.3.12" + "@storybook/api" "6.3.12" + "@storybook/channels" "6.3.12" + "@storybook/client-api" "6.3.12" + "@storybook/client-logger" "6.3.12" + "@storybook/components" "6.3.12" + "@storybook/core-events" "6.3.12" + "@storybook/theming" "6.3.12" axe-core "^4.2.0" core-js "^3.8.2" global "^4.4.0" @@ -5546,6 +5546,21 @@ global "^4.4.0" regenerator-runtime "^0.13.7" +"@storybook/addons@6.3.12", "@storybook/addons@^6.1.11": + version "6.3.12" + resolved "https://registry.npmjs.org/@storybook/addons/-/addons-6.3.12.tgz#8773dcc113c5086dfff722388b7b65580e43b65b" + integrity sha512-UgoMyr7Qr0FS3ezt8u6hMEcHgyynQS9ucr5mAwZky3wpXRPFyUTmMto9r4BBUdqyUvTUj/LRKIcmLBfj+/l0Fg== + dependencies: + "@storybook/api" "6.3.12" + "@storybook/channels" "6.3.12" + "@storybook/client-logger" "6.3.12" + "@storybook/core-events" "6.3.12" + "@storybook/router" "6.3.12" + "@storybook/theming" "6.3.12" + core-js "^3.8.2" + global "^4.4.0" + regenerator-runtime "^0.13.7" + "@storybook/addons@6.3.7": version "6.3.7" resolved "https://registry.npmjs.org/@storybook/addons/-/addons-6.3.7.tgz#7c6b8d11b65f67b1884f6140437fe996dc39537a" @@ -5561,21 +5576,6 @@ global "^4.4.0" regenerator-runtime "^0.13.7" -"@storybook/addons@^6.1.11": - version "6.3.12" - resolved "https://registry.npmjs.org/@storybook/addons/-/addons-6.3.12.tgz#8773dcc113c5086dfff722388b7b65580e43b65b" - integrity sha512-UgoMyr7Qr0FS3ezt8u6hMEcHgyynQS9ucr5mAwZky3wpXRPFyUTmMto9r4BBUdqyUvTUj/LRKIcmLBfj+/l0Fg== - dependencies: - "@storybook/api" "6.3.12" - "@storybook/channels" "6.3.12" - "@storybook/client-logger" "6.3.12" - "@storybook/core-events" "6.3.12" - "@storybook/router" "6.3.12" - "@storybook/theming" "6.3.12" - core-js "^3.8.2" - global "^4.4.0" - regenerator-runtime "^0.13.7" - "@storybook/api@6.3.11": version "6.3.11" resolved "https://registry.npmjs.org/@storybook/api/-/api-6.3.11.tgz#ea3806a0570da65bfb5b39e4edb90289b5ba701e" @@ -5743,6 +5743,19 @@ qs "^6.10.0" telejson "^5.3.2" +"@storybook/channel-postmessage@6.3.12": + version "6.3.12" + resolved "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-6.3.12.tgz#3ff9412ac0f445e3b8b44dd414e783a5a47ff7c1" + integrity sha512-Ou/2Ga3JRTZ/4sSv7ikMgUgLTeZMsXXWLXuscz4oaYhmOqAU9CrJw0G1NitwBgK/+qC83lEFSLujHkWcoQDOKg== + dependencies: + "@storybook/channels" "6.3.12" + "@storybook/client-logger" "6.3.12" + "@storybook/core-events" "6.3.12" + core-js "^3.8.2" + global "^4.4.0" + qs "^6.10.0" + telejson "^5.3.2" + "@storybook/channel-postmessage@6.3.7": version "6.3.7" resolved "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-6.3.7.tgz#bd4edf84a29aa2cd4a22d26115c60194d289a840" @@ -5807,6 +5820,30 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/client-api@6.3.12": + version "6.3.12" + resolved "https://registry.npmjs.org/@storybook/client-api/-/client-api-6.3.12.tgz#a0c6d72a871d1cb02b4b98675472839061e39b5b" + integrity sha512-xnW+lKKK2T774z+rOr9Wopt1aYTStfb86PSs9p3Fpnc2Btcftln+C3NtiHZl8Ccqft8Mz/chLGgewRui6tNI8g== + dependencies: + "@storybook/addons" "6.3.12" + "@storybook/channel-postmessage" "6.3.12" + "@storybook/channels" "6.3.12" + "@storybook/client-logger" "6.3.12" + "@storybook/core-events" "6.3.12" + "@storybook/csf" "0.0.1" + "@types/qs" "^6.9.5" + "@types/webpack-env" "^1.16.0" + core-js "^3.8.2" + global "^4.4.0" + lodash "^4.17.20" + memoizerific "^1.11.3" + qs "^6.10.0" + regenerator-runtime "^0.13.7" + stable "^0.1.8" + store2 "^2.12.0" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/client-api@6.3.7": version "6.3.7" resolved "https://registry.npmjs.org/@storybook/client-api/-/client-api-6.3.7.tgz#cb1dca05467d777bd09aadbbdd1dd22ca537ce14" @@ -5885,6 +5922,36 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/components@6.3.12": + version "6.3.12" + resolved "https://registry.npmjs.org/@storybook/components/-/components-6.3.12.tgz#0c7967c60354c84afa20dfab4753105e49b1927d" + integrity sha512-kdQt8toUjynYAxDLrJzuG7YSNL6as1wJoyzNUaCfG06YPhvIAlKo7le9tS2mThVFN5e9nbKrW3N1V1sp6ypZXQ== + dependencies: + "@popperjs/core" "^2.6.0" + "@storybook/client-logger" "6.3.12" + "@storybook/csf" "0.0.1" + "@storybook/theming" "6.3.12" + "@types/color-convert" "^2.0.0" + "@types/overlayscrollbars" "^1.12.0" + "@types/react-syntax-highlighter" "11.0.5" + color-convert "^2.0.1" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.20" + markdown-to-jsx "^7.1.3" + memoizerific "^1.11.3" + overlayscrollbars "^1.13.1" + polished "^4.0.5" + prop-types "^15.7.2" + react-colorful "^5.1.2" + react-popper-tooltip "^3.1.1" + react-syntax-highlighter "^13.5.3" + react-textarea-autosize "^8.3.0" + regenerator-runtime "^0.13.7" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/components@6.3.7": version "6.3.7" resolved "https://registry.npmjs.org/@storybook/components/-/components-6.3.7.tgz#42b1ca6d24e388e02eab82aa9ed3365db2266ecc" From bd74a61eaabca24e8cdcedefe70beb21e6bff135 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Mon, 8 Nov 2021 11:03:58 +0000 Subject: [PATCH 51/65] Updated to run on a separate workflow every 4 hours. Signed-off-by: Harry Hogg --- .github/workflows/snyk-github-issue-sync.yml | 23 ++++++++++++++++++++ .github/workflows/snyk-monitor.yml | 5 ----- 2 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/snyk-github-issue-sync.yml diff --git a/.github/workflows/snyk-github-issue-sync.yml b/.github/workflows/snyk-github-issue-sync.yml new file mode 100644 index 0000000000..34eb5c1ca4 --- /dev/null +++ b/.github/workflows/snyk-github-issue-sync.yml @@ -0,0 +1,23 @@ +name: 'Snyk Github Issue Sync' + +on: + schedule: + - cron: '0 */4 * * *' + +jobs: + sync: + steps: + - uses: actions/checkout@v2 + - name: Create Snyk report + uses: snyk/actions/node@master + with: + args: > + --yarn-workspaces + --org=backstage-dgh + --strict-out-of-sync=false + --json-file-output=snyk.json + json: true + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + - name: Update Github issues + run: yarn ts-node scripts/snyk-github-issue-sync.ts diff --git a/.github/workflows/snyk-monitor.yml b/.github/workflows/snyk-monitor.yml index 32df37f35b..0adb6c5bcb 100644 --- a/.github/workflows/snyk-monitor.yml +++ b/.github/workflows/snyk-monitor.yml @@ -43,14 +43,9 @@ jobs: --org=backstage-dgh --strict-out-of-sync=false --sarif-file-output=snyk.sarif - --json-file-output=snyk.json - json: true env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - name: Upload Snyk report uses: github/codeql-action/upload-sarif@v1 with: sarif_file: snyk.sarif - - - name: Update Github issues - run: yarn ts-node scripts/snyk-github-issue-sync.ts From 26c5659c97b6d57859f8e3fca68f66c1663a76d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 8 Nov 2021 15:02:07 +0100 Subject: [PATCH 52/65] Bump msw to the same version as the rest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/odd-rats-walk.md | 5 +++++ packages/config-loader/package.json | 2 +- yarn.lock | 28 ---------------------------- 3 files changed, 6 insertions(+), 29 deletions(-) create mode 100644 .changeset/odd-rats-walk.md diff --git a/.changeset/odd-rats-walk.md b/.changeset/odd-rats-walk.md new file mode 100644 index 0000000000..3a8710cc32 --- /dev/null +++ b/.changeset/odd-rats-walk.md @@ -0,0 +1,5 @@ +--- +'@backstage/config-loader': patch +--- + +Bump msw to the same version as the rest diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 910dd87108..22fcedf486 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -53,7 +53,7 @@ "@types/node": "^14.14.32", "@types/yup": "^0.29.13", "mock-fs": "^5.1.0", - "msw": "^0.29.0" + "msw": "^0.35.0" }, "files": [ "dist" diff --git a/yarn.lock b/yarn.lock index 0d88beceb5..6dc17c5577 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14206,11 +14206,6 @@ fecha@^4.2.0: resolved "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz#3ffb6395453e3f3efff850404f0a59b6747f5f41" integrity sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg== -fetch-blob@2.1.2: - version "2.1.2" - resolved "https://registry.npmjs.org/fetch-blob/-/fetch-blob-2.1.2.tgz#a7805db1361bd44c1ef62bb57fb5fe8ea173ef3c" - integrity sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow== - fetch-readablestream@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/fetch-readablestream/-/fetch-readablestream-0.2.0.tgz#eaa6d1a76b12de2d4731a343393c6ccdcfe2c795" @@ -28357,11 +28352,6 @@ webidl-conversions@^3.0.0: resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= -webidl-conversions@^4.0.2: - version "4.0.2" - resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" - integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== - webidl-conversions@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" @@ -28585,24 +28575,6 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" -whatwg-url@^6.4.1: - version "6.5.0" - resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" - integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ== - dependencies: - lodash.sortby "^4.7.0" - tr46 "^1.0.1" - webidl-conversions "^4.0.2" - -whatwg-url@^7.0.0: - version "7.1.0" - resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" - integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== - dependencies: - lodash.sortby "^4.7.0" - tr46 "^1.0.1" - webidl-conversions "^4.0.2" - whatwg-url@^8.0.0, whatwg-url@^8.4.0: version "8.4.0" resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.4.0.tgz#50fb9615b05469591d2b2bd6dfaed2942ed72837" From 0557332ec60fb9a8a322768fddb20e6385561106 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 8 Nov 2021 15:05:56 +0100 Subject: [PATCH 53/65] chore: missing ADR in sidebars Signed-off-by: blam --- microsite/sidebars.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microsite/sidebars.json b/microsite/sidebars.json index bee9cd7180..23cc6135c0 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -275,7 +275,8 @@ "architecture-decisions/adrs-adr008", "architecture-decisions/adrs-adr009", "architecture-decisions/adrs-adr010", - "architecture-decisions/adrs-adr011" + "architecture-decisions/adrs-adr011", + "architecture-decisions/adrs-adr012" ], "FAQ": ["FAQ"] } From 9c76bf58b7c31f632700f8033f7b4bf10bf88c97 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Mon, 8 Nov 2021 14:06:44 +0000 Subject: [PATCH 54/65] feat: Created new `@backstage/plugin-azure-devops-common` package for common types. Signed-off-by: Marley Powell --- .changeset/tough-buckets-explain.md | 3 +- plugins/azure-devops-common/.eslintrc.js | 3 + plugins/azure-devops-common/package.json | 37 +++++++++ plugins/azure-devops-common/src/index.ts | 17 ++++ plugins/azure-devops-common/src/types.ts | 82 +++++++++++++++++++ plugins/azure-devops/package.json | 2 +- .../BuildTable/BuildTable.stories.tsx | 2 +- 7 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 plugins/azure-devops-common/.eslintrc.js create mode 100644 plugins/azure-devops-common/package.json create mode 100644 plugins/azure-devops-common/src/index.ts create mode 100644 plugins/azure-devops-common/src/types.ts diff --git a/.changeset/tough-buckets-explain.md b/.changeset/tough-buckets-explain.md index a2ca37d516..53d397a4ef 100644 --- a/.changeset/tough-buckets-explain.md +++ b/.changeset/tough-buckets-explain.md @@ -1,5 +1,6 @@ --- '@backstage/plugin-azure-devops': patch +'@backstage/plugin-azure-devops-common': patch --- -Simplified queue time calculation in `BuildTable`. +Simplified queue time calculation in `BuildTable`. Created new `@backstage/plugin-azure-devops-common` package. diff --git a/plugins/azure-devops-common/.eslintrc.js b/plugins/azure-devops-common/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/plugins/azure-devops-common/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/plugins/azure-devops-common/package.json b/plugins/azure-devops-common/package.json new file mode 100644 index 0000000000..e1e5d00894 --- /dev/null +++ b/plugins/azure-devops-common/package.json @@ -0,0 +1,37 @@ +{ + "name": "@backstage/plugin-azure-devops-common", + "version": "0.0.1", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "module": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/azure-devops-common" + }, + "keywords": [ + "backstage" + ], + "scripts": { + "build": "backstage-cli build", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "devDependencies": { + "@backstage/cli": "^0.8.1" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/azure-devops-common/src/index.ts b/plugins/azure-devops-common/src/index.ts new file mode 100644 index 0000000000..26a854de89 --- /dev/null +++ b/plugins/azure-devops-common/src/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 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 * from './types'; diff --git a/plugins/azure-devops-common/src/types.ts b/plugins/azure-devops-common/src/types.ts new file mode 100644 index 0000000000..d7736d03e1 --- /dev/null +++ b/plugins/azure-devops-common/src/types.ts @@ -0,0 +1,82 @@ +/* + * Copyright 2021 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 enum BuildResult { + /** + * No result + */ + None = 0, + /** + * The build completed successfully. + */ + Succeeded = 2, + /** + * The build completed compilation successfully but had other errors. + */ + PartiallySucceeded = 4, + /** + * The build completed unsuccessfully. + */ + Failed = 8, + /** + * The build was canceled before starting. + */ + Canceled = 32, +} + +export enum BuildStatus { + /** + * No status. + */ + None = 0, + /** + * The build is currently in progress. + */ + InProgress = 1, + /** + * The build has completed. + */ + Completed = 2, + /** + * The build is cancelling + */ + Cancelling = 4, + /** + * The build is inactive in the queue. + */ + Postponed = 8, + /** + * The build has not yet started. + */ + NotStarted = 32, + /** + * All status. + */ + All = 47, +} + +export type RepoBuild = { + id?: number; + title: string; + link?: string; + status?: BuildStatus; + result?: BuildResult; + queueTime?: Date; + startTime?: Date; + finishTime?: Date; + source: string; + uniqueName?: string; +}; diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json index 1c5e0bba84..1ad5b07ebb 100644 --- a/plugins/azure-devops/package.json +++ b/plugins/azure-devops/package.json @@ -31,7 +31,7 @@ "@backstage/core-components": "^0.7.2", "@backstage/core-plugin-api": "^0.1.12", "@backstage/errors": "^0.1.4", - "@backstage/plugin-azure-devops-backend": "^0.1.4", + "@backstage/plugin-azure-devops-common": "^0.0.1", "@backstage/plugin-catalog-react": "^0.6.2", "@backstage/theme": "^0.2.12", "@material-ui/core": "^4.12.2", diff --git a/plugins/azure-devops/src/components/BuildTable/BuildTable.stories.tsx b/plugins/azure-devops/src/components/BuildTable/BuildTable.stories.tsx index 609466769b..76a19155ba 100644 --- a/plugins/azure-devops/src/components/BuildTable/BuildTable.stories.tsx +++ b/plugins/azure-devops/src/components/BuildTable/BuildTable.stories.tsx @@ -18,7 +18,7 @@ import { BuildResult, BuildStatus, RepoBuild, -} from '@backstage/plugin-azure-devops-backend'; +} from '@backstage/plugin-azure-devops-common'; import { BuildTable } from './BuildTable'; import { MemoryRouter } from 'react-router'; From a209473de859a5d58a28f7da5dc665fec56d6dca Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Mon, 8 Nov 2021 14:08:25 +0000 Subject: [PATCH 55/65] revert: Removed unnecessary changes in changeset. Signed-off-by: Marley Powell --- .changeset/tough-buckets-explain.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.changeset/tough-buckets-explain.md b/.changeset/tough-buckets-explain.md index 53d397a4ef..a2ca37d516 100644 --- a/.changeset/tough-buckets-explain.md +++ b/.changeset/tough-buckets-explain.md @@ -1,6 +1,5 @@ --- '@backstage/plugin-azure-devops': patch -'@backstage/plugin-azure-devops-common': patch --- -Simplified queue time calculation in `BuildTable`. Created new `@backstage/plugin-azure-devops-common` package. +Simplified queue time calculation in `BuildTable`. From 4c780d86514c0b4354357cda21ffead175202a4b Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 8 Nov 2021 15:14:38 +0100 Subject: [PATCH 56/65] chore: use named exports instead Signed-off-by: blam --- plugins/catalog-react/src/hooks/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/catalog-react/src/hooks/index.ts b/plugins/catalog-react/src/hooks/index.ts index 17c15e4ebb..bbbca3a501 100644 --- a/plugins/catalog-react/src/hooks/index.ts +++ b/plugins/catalog-react/src/hooks/index.ts @@ -37,4 +37,8 @@ export { useOwnUser } from './useOwnUser'; export { useRelatedEntities } from './useRelatedEntities'; export { useStarredEntities } from './useStarredEntities'; export { useStarredEntity } from './useStarredEntity'; -export * from './useEntityOwnership'; +export { + loadCatalogOwnerRefs, + useEntityOwnership, + loadIdentityOwnerRefs, +} from './useEntityOwnership'; From 4634588de330ca63e68ff8af136a423bc03b3303 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Mon, 8 Nov 2021 14:29:50 +0000 Subject: [PATCH 57/65] chore: generated API report. Signed-off-by: Marley Powell --- plugins/azure-devops-common/api-report.md | 47 +++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 plugins/azure-devops-common/api-report.md diff --git a/plugins/azure-devops-common/api-report.md b/plugins/azure-devops-common/api-report.md new file mode 100644 index 0000000000..0bc6568189 --- /dev/null +++ b/plugins/azure-devops-common/api-report.md @@ -0,0 +1,47 @@ +## API Report File for "@backstage/plugin-azure-devops-common" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +// Warning: (ae-missing-release-tag) "BuildResult" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export enum BuildResult { + Canceled = 32, + Failed = 8, + None = 0, + PartiallySucceeded = 4, + Succeeded = 2, +} + +// Warning: (ae-missing-release-tag) "BuildStatus" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export enum BuildStatus { + All = 47, + Cancelling = 4, + Completed = 2, + InProgress = 1, + None = 0, + NotStarted = 32, + Postponed = 8, +} + +// Warning: (ae-missing-release-tag) "RepoBuild" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type RepoBuild = { + id?: number; + title: string; + link?: string; + status?: BuildStatus; + result?: BuildResult; + queueTime?: Date; + startTime?: Date; + finishTime?: Date; + source: string; + uniqueName?: string; +}; + +// (No @packageDocumentation comment for this package) +``` From 0c9dd8df5143f3ee906de63a6b351fe3b0d3be40 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Mon, 8 Nov 2021 14:56:16 +0000 Subject: [PATCH 58/65] fix: Added `--passWithNoTests` flag to `package.json`. Signed-off-by: Marley Powell --- plugins/azure-devops-common/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/azure-devops-common/package.json b/plugins/azure-devops-common/package.json index e1e5d00894..05f70f6596 100644 --- a/plugins/azure-devops-common/package.json +++ b/plugins/azure-devops-common/package.json @@ -23,7 +23,7 @@ "scripts": { "build": "backstage-cli build", "lint": "backstage-cli lint", - "test": "backstage-cli test", + "test": "backstage-cli test --passWithNoTests", "prepack": "backstage-cli prepack", "postpack": "backstage-cli postpack", "clean": "backstage-cli clean" From 86bef79ad1eaa499c0cd9afe443d4cd1029f22f4 Mon Sep 17 00:00:00 2001 From: Joon Park Date: Fri, 29 Oct 2021 15:31:46 +0100 Subject: [PATCH 59/65] Allow nested EntityFilters This makes the format of EntityFilters more flexible, and paves the way for the permissions system, which requires composing multiple _collections_ of filters. Signed-off-by: Joon Park --- .changeset/slimy-days-leave.md | 5 + plugins/catalog-backend/api-report.md | 2 +- plugins/catalog-backend/src/catalog/types.ts | 2 +- .../src/legacy/database/CommonDatabase.ts | 24 ++- .../src/service/NextEntitiesCatalog.test.ts | 155 ++++++++++++++++++ .../src/service/NextEntitiesCatalog.ts | 97 +++++++---- 6 files changed, 242 insertions(+), 43 deletions(-) create mode 100644 .changeset/slimy-days-leave.md diff --git a/.changeset/slimy-days-leave.md b/.changeset/slimy-days-leave.md new file mode 100644 index 0000000000..250bf32962 --- /dev/null +++ b/.changeset/slimy-days-leave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Allow nested EntityFilters diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 6d21884c12..e83fa68cad 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -872,7 +872,7 @@ export type EntityAncestryResponse = { // @public export type EntityFilter = { anyOf: { - allOf: EntitiesSearchFilter[]; + allOf: (EntitiesSearchFilter | EntityFilter)[]; }[]; }; diff --git a/plugins/catalog-backend/src/catalog/types.ts b/plugins/catalog-backend/src/catalog/types.ts index 0bc0ec4311..b0b06c563c 100644 --- a/plugins/catalog-backend/src/catalog/types.ts +++ b/plugins/catalog-backend/src/catalog/types.ts @@ -23,7 +23,7 @@ import { Entity, EntityRelationSpec } from '@backstage/catalog-model'; * individual filters must match. */ export type EntityFilter = { - anyOf: { allOf: EntitiesSearchFilter[] }[]; + anyOf: { allOf: (EntitiesSearchFilter | EntityFilter)[] }[]; }; /** diff --git a/plugins/catalog-backend/src/legacy/database/CommonDatabase.ts b/plugins/catalog-backend/src/legacy/database/CommonDatabase.ts index 5cc31b5be5..81b0612989 100644 --- a/plugins/catalog-backend/src/legacy/database/CommonDatabase.ts +++ b/plugins/catalog-backend/src/legacy/database/CommonDatabase.ts @@ -46,7 +46,11 @@ import { DbPageInfo, Transaction, } from './types'; -import { EntityPagination } from '../../catalog/types'; +import { + EntityPagination, + EntityFilter, + EntitiesSearchFilter, +} from '../../catalog/types'; // The number of items that are sent per batch to the database layer, when // doing .batchInsert calls to knex. This needs to be low enough to not cause @@ -219,11 +223,13 @@ export class CommonDatabase implements Database { for (const singleFilter of request?.filter?.anyOf ?? []) { entitiesQuery = entitiesQuery.orWhere(function singleFilterFn() { - for (const { - key, - matchValueIn, - matchValueExists, - } of singleFilter.allOf) { + for (const filter of singleFilter.allOf) { + if (isEntityFilter(filter)) { + throw new Error( + 'Nested filters are not supported in the legacy CommonDatabase', + ); + } + const { key, matchValueIn, matchValueExists } = filter; // NOTE(freben): This used to be a set of OUTER JOIN, which may seem to // make a lot of sense. However, it had abysmal performance on sqlite // when datasets grew large, so we're using IN instead. @@ -606,3 +612,9 @@ function deduplicateRelations( r => `${r.source_full_name}:${r.target_full_name}:${r.type}`, ); } + +function isEntityFilter( + filter: EntitiesSearchFilter | EntityFilter, +): filter is EntityFilter { + return filter.hasOwnProperty('anyOf'); +} diff --git a/plugins/catalog-backend/src/service/NextEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/NextEntitiesCatalog.test.ts index 082aa47df5..0c805e1b17 100644 --- a/plugins/catalog-backend/src/service/NextEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/NextEntitiesCatalog.test.ts @@ -23,6 +23,7 @@ import { DbFinalEntitiesRow, DbRefreshStateReferencesRow, DbRefreshStateRow, + DbSearchRow, } from '../database/tables'; import { NextEntitiesCatalog } from './NextEntitiesCatalog'; @@ -73,6 +74,52 @@ describe('NextEntitiesCatalog', () => { } } + async function addEntityToSearch(knex: Knex, entity: Entity) { + const id = uuid(); + const entityRef = stringifyEntityRef(entity); + const entityJson = JSON.stringify(entity); + + await knex('refresh_state').insert({ + entity_id: id, + entity_ref: entityRef, + unprocessed_entity: entityJson, + errors: '[]', + next_update_at: '2031-01-01 23:00:00', + last_discovery_at: '2021-04-01 13:37:00', + }); + + await knex('final_entities').insert({ + entity_id: id, + final_entity: entityJson, + hash: 'h', + stitch_ticket: '', + }); + + await insertSearchRow(knex, id, null, entity); + } + + async function insertSearchRow( + knex: Knex, + id: string, + previousKey: string | null, + previousValue: Object, + ) { + return Promise.all( + Object.entries(previousValue).map(async ([key, value]) => { + const currentKey = `${previousKey ? `${previousKey}.` : ``}${key}`; + if (typeof value === 'object') { + await insertSearchRow(knex, id, currentKey, value); + } else { + await knex('search').insert({ + entity_id: id, + key: currentKey, + value: value, + }); + } + }), + ); + } + describe('entityAncestry', () => { it.each(databases.eachSupportedId())( 'should return the ancestry with one parent, %p', @@ -209,4 +256,112 @@ describe('NextEntitiesCatalog', () => { 60_000, ); }); + + describe('entities', () => { + it.each(databases.eachSupportedId())( + 'should return correct entity for simple filter', + async databaseId => { + const { knex } = await createDatabase(databaseId); + const entity1: Entity = { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'one' }, + spec: {}, + }; + const entity2: Entity = { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'two' }, + spec: { + test: 'test value', + }, + }; + await addEntityToSearch(knex, entity1); + await addEntityToSearch(knex, entity2); + const catalog = new NextEntitiesCatalog(knex); + + const testFilter = { + key: 'spec.test', + matchValueExists: true, + }; + const request = { + filter: { anyOf: [{ allOf: [testFilter] }] }, + }; + const { entities } = await catalog.entities(request); + + expect(entities.length).toBe(1); + expect(entities[0]).toEqual(entity2); + }, + ); + + it.each(databases.eachSupportedId())( + 'should return correct entity for nested filter', + async databaseId => { + const { knex } = await createDatabase(databaseId); + const entity1: Entity = { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'one', org: 'a', desc: 'description' }, + spec: {}, + }; + const entity2: Entity = { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'two', org: 'b', desc: 'description' }, + spec: {}, + }; + const entity3: Entity = { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'three', org: 'b', color: 'red' }, + spec: {}, + }; + const entity4: Entity = { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'four', org: 'b', color: 'blue' }, + spec: {}, + }; + await addEntityToSearch(knex, entity1); + await addEntityToSearch(knex, entity2); + await addEntityToSearch(knex, entity3); + await addEntityToSearch(knex, entity4); + const catalog = new NextEntitiesCatalog(knex); + + const testFilter1 = { + key: 'metadata.org', + matchValueExists: true, + matchValueIn: ['b'], + }; + const testFilter2 = { + key: 'metadata.desc', + matchValueExists: true, + }; + const testFilter3 = { + key: 'metadata.color', + matchValueExists: true, + matchValueIn: ['blue'], + }; + const request = { + filter: { + anyOf: [ + { + allOf: [ + testFilter1, + { + anyOf: [{ allOf: [testFilter2] }, { allOf: [testFilter3] }], + }, + ], + }, + ], + }, + }; + const { entities } = await catalog.entities(request); + + expect(entities.length).toBe(2); + expect(entities).toContainEqual(entity2); + expect(entities).toContainEqual(entity4); + }, + ); + }); }); diff --git a/plugins/catalog-backend/src/service/NextEntitiesCatalog.ts b/plugins/catalog-backend/src/service/NextEntitiesCatalog.ts index 0891be045d..52d6ed1bdd 100644 --- a/plugins/catalog-backend/src/service/NextEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/service/NextEntitiesCatalog.ts @@ -23,6 +23,8 @@ import { EntitiesResponse, EntityAncestryResponse, EntityPagination, + EntityFilter, + EntitiesSearchFilter, } from '../catalog/types'; import { DbFinalEntitiesRow, @@ -73,6 +75,64 @@ function stringifyPagination(input: { limit: number; offset: number }) { return base64; } +function addCondition( + queryBuilder: Knex.QueryBuilder, + db: Knex, + { key, matchValueIn, matchValueExists }: EntitiesSearchFilter, +) { + // NOTE(freben): This used to be a set of OUTER JOIN, which may seem to + // make a lot of sense. However, it had abysmal performance on sqlite + // when datasets grew large, so we're using IN instead. + const matchQuery = db('search') + .select('entity_id') + .where(function keyFilter() { + this.andWhere({ key: key.toLowerCase() }); + if (matchValueExists !== false && matchValueIn) { + if (matchValueIn.length === 1) { + this.andWhere({ value: matchValueIn[0].toLowerCase() }); + } else if (matchValueIn.length > 1) { + this.andWhere( + 'value', + 'in', + matchValueIn.map(v => v.toLowerCase()), + ); + } + } + }); + // Explicitly evaluate matchValueExists as a boolean since it may be undefined + queryBuilder.andWhere( + 'entity_id', + matchValueExists === false ? 'not in' : 'in', + matchQuery, + ); +} + +function isEntityFilter( + filter: EntitiesSearchFilter | EntityFilter, +): filter is EntityFilter { + return filter.hasOwnProperty('anyOf'); +} + +function parseFilter( + filters: EntityFilter, + query: Knex.QueryBuilder, + db: Knex, +): Knex.QueryBuilder { + let cumulativeQuery = query; + for (const singleFilter of filters?.anyOf ?? []) { + cumulativeQuery = cumulativeQuery.orWhere(function singleFilterFn() { + for (const filter of singleFilter.allOf) { + if (isEntityFilter(filter)) { + this.andWhere(subQuery => parseFilter(filter, subQuery, db)); + } else { + addCondition(this, db, filter); + } + } + }); + } + return cumulativeQuery; +} + export class NextEntitiesCatalog implements EntitiesCatalog { constructor(private readonly database: Knex) {} @@ -80,41 +140,8 @@ export class NextEntitiesCatalog implements EntitiesCatalog { const db = this.database; let entitiesQuery = db('final_entities'); - - for (const singleFilter of request?.filter?.anyOf ?? []) { - entitiesQuery = entitiesQuery.orWhere(function singleFilterFn() { - for (const { - key, - matchValueIn, - matchValueExists, - } of singleFilter.allOf) { - // NOTE(freben): This used to be a set of OUTER JOIN, which may seem to - // make a lot of sense. However, it had abysmal performance on sqlite - // when datasets grew large, so we're using IN instead. - const matchQuery = db('search') - .select('entity_id') - .where(function keyFilter() { - this.andWhere({ key: key.toLowerCase() }); - if (matchValueExists !== false && matchValueIn) { - if (matchValueIn.length === 1) { - this.andWhere({ value: matchValueIn[0].toLowerCase() }); - } else if (matchValueIn.length > 1) { - this.andWhere( - 'value', - 'in', - matchValueIn.map(v => v.toLowerCase()), - ); - } - } - }); - // Explicitly evaluate matchValueExists as a boolean since it may be undefined - this.andWhere( - 'entity_id', - matchValueExists === false ? 'not in' : 'in', - matchQuery, - ); - } - }); + if (request?.filter) { + entitiesQuery = parseFilter(request.filter, entitiesQuery, db); } // TODO: move final_entities to use entity_ref From c74834a62d9e5208d16754eaaf42df3c228f97e4 Mon Sep 17 00:00:00 2001 From: Joon Park Date: Mon, 8 Nov 2021 12:31:28 +0000 Subject: [PATCH 60/65] Allow singleton and flexible EntityFilters. Signed-off-by: Joon Park --- .changeset/slimy-days-leave.md | 2 +- plugins/catalog-backend/api-report.md | 13 +++-- plugins/catalog-backend/src/catalog/types.ts | 7 +-- .../src/legacy/database/CommonDatabase.ts | 33 +++++++----- .../src/service/NextEntitiesCatalog.test.ts | 14 ++--- .../src/service/NextEntitiesCatalog.ts | 54 ++++++++++++++----- 6 files changed, 77 insertions(+), 46 deletions(-) diff --git a/.changeset/slimy-days-leave.md b/.changeset/slimy-days-leave.md index 250bf32962..df772d80d4 100644 --- a/.changeset/slimy-days-leave.md +++ b/.changeset/slimy-days-leave.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-backend': patch --- -Allow nested EntityFilters +Allow singleton and flexibly nested EntityFilters diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index e83fa68cad..143e31bc21 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -870,11 +870,14 @@ export type EntityAncestryResponse = { // Warning: (ae-missing-release-tag) "EntityFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public -export type EntityFilter = { - anyOf: { - allOf: (EntitiesSearchFilter | EntityFilter)[]; - }[]; -}; +export type EntityFilter = + | { + allOf: EntityFilter[]; + } + | { + anyOf: EntityFilter[]; + } + | EntitiesSearchFilter; // Warning: (ae-missing-release-tag) "EntityPagination" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/plugins/catalog-backend/src/catalog/types.ts b/plugins/catalog-backend/src/catalog/types.ts index b0b06c563c..df693bfd42 100644 --- a/plugins/catalog-backend/src/catalog/types.ts +++ b/plugins/catalog-backend/src/catalog/types.ts @@ -22,9 +22,10 @@ import { Entity, EntityRelationSpec } from '@backstage/catalog-model'; * Any (at least one) of the outer sets must match, within which all of the * individual filters must match. */ -export type EntityFilter = { - anyOf: { allOf: (EntitiesSearchFilter | EntityFilter)[] }[]; -}; +export type EntityFilter = + | { allOf: EntityFilter[] } + | { anyOf: EntityFilter[] } + | EntitiesSearchFilter; /** * A pagination rule for entities. diff --git a/plugins/catalog-backend/src/legacy/database/CommonDatabase.ts b/plugins/catalog-backend/src/legacy/database/CommonDatabase.ts index 81b0612989..6a150f7583 100644 --- a/plugins/catalog-backend/src/legacy/database/CommonDatabase.ts +++ b/plugins/catalog-backend/src/legacy/database/CommonDatabase.ts @@ -46,11 +46,11 @@ import { DbPageInfo, Transaction, } from './types'; -import { - EntityPagination, - EntityFilter, - EntitiesSearchFilter, -} from '../../catalog/types'; +import { EntityPagination, EntitiesSearchFilter } from '../../catalog/types'; + +type LegacyEntityFilter = { + anyOf: { allOf: EntitiesSearchFilter[] }[]; +}; // The number of items that are sent per batch to the database layer, when // doing .batchInsert calls to knex. This needs to be low enough to not cause @@ -221,10 +221,23 @@ export class CommonDatabase implements Database { let entitiesQuery = tx('entities'); - for (const singleFilter of request?.filter?.anyOf ?? []) { + if ( + request?.filter && + (request.filter.hasOwnProperty('key') || + request.filter.hasOwnProperty('allOf')) + ) { + throw new Error( + 'Filters for the legacy CommonDatabase must obey the { anyOf: [{ allOf: [] }] } format.', + ); + } + for (const singleFilter of (request?.filter as LegacyEntityFilter)?.anyOf ?? + []) { entitiesQuery = entitiesQuery.orWhere(function singleFilterFn() { for (const filter of singleFilter.allOf) { - if (isEntityFilter(filter)) { + if ( + filter.hasOwnProperty('anyOf') || + filter.hasOwnProperty('allOf') + ) { throw new Error( 'Nested filters are not supported in the legacy CommonDatabase', ); @@ -612,9 +625,3 @@ function deduplicateRelations( r => `${r.source_full_name}:${r.target_full_name}:${r.type}`, ); } - -function isEntityFilter( - filter: EntitiesSearchFilter | EntityFilter, -): filter is EntityFilter { - return filter.hasOwnProperty('anyOf'); -} diff --git a/plugins/catalog-backend/src/service/NextEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/NextEntitiesCatalog.test.ts index 0c805e1b17..32d5fa843e 100644 --- a/plugins/catalog-backend/src/service/NextEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/NextEntitiesCatalog.test.ts @@ -284,9 +284,7 @@ describe('NextEntitiesCatalog', () => { key: 'spec.test', matchValueExists: true, }; - const request = { - filter: { anyOf: [{ allOf: [testFilter] }] }, - }; + const request = { filter: testFilter }; const { entities } = await catalog.entities(request); expect(entities.length).toBe(1); @@ -344,14 +342,10 @@ describe('NextEntitiesCatalog', () => { }; const request = { filter: { - anyOf: [ + allOf: [ + testFilter1, { - allOf: [ - testFilter1, - { - anyOf: [{ allOf: [testFilter2] }, { allOf: [testFilter3] }], - }, - ], + anyOf: [testFilter2, testFilter3], }, ], }, diff --git a/plugins/catalog-backend/src/service/NextEntitiesCatalog.ts b/plugins/catalog-backend/src/service/NextEntitiesCatalog.ts index 52d6ed1bdd..841e57c921 100644 --- a/plugins/catalog-backend/src/service/NextEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/service/NextEntitiesCatalog.ts @@ -107,30 +107,56 @@ function addCondition( ); } -function isEntityFilter( +function isEntitiesSearchFilter( filter: EntitiesSearchFilter | EntityFilter, -): filter is EntityFilter { +): filter is EntitiesSearchFilter { + return filter.hasOwnProperty('key'); +} + +function isAndEntityFilter( + filter: { allOf: EntityFilter[] } | EntityFilter, +): filter is { allOf: EntityFilter[] } { + return filter.hasOwnProperty('allOf'); +} + +function isOrEntityFilter( + filter: { anyOf: EntityFilter[] } | EntityFilter, +): filter is { anyOf: EntityFilter[] } { return filter.hasOwnProperty('anyOf'); } function parseFilter( - filters: EntityFilter, + filter: EntityFilter, query: Knex.QueryBuilder, db: Knex, ): Knex.QueryBuilder { - let cumulativeQuery = query; - for (const singleFilter of filters?.anyOf ?? []) { - cumulativeQuery = cumulativeQuery.orWhere(function singleFilterFn() { - for (const filter of singleFilter.allOf) { - if (isEntityFilter(filter)) { - this.andWhere(subQuery => parseFilter(filter, subQuery, db)); - } else { - addCondition(this, db, filter); - } - } + if (isEntitiesSearchFilter(filter)) { + return query.where(function filterFunction() { + addCondition(this, db, filter); }); } - return cumulativeQuery; + + if (isOrEntityFilter(filter)) { + let cumulativeQuery = query; + for (const subFilter of filter.anyOf ?? []) { + cumulativeQuery = cumulativeQuery.orWhere(subQuery => + parseFilter(subFilter, subQuery, db), + ); + } + return cumulativeQuery; + } + + if (isAndEntityFilter(filter)) { + let cumulativeQuery = query; + for (const subFilter of filter.allOf ?? []) { + cumulativeQuery = cumulativeQuery.andWhere(subQuery => + parseFilter(subFilter, subQuery, db), + ); + } + return cumulativeQuery; + } + + return query; } export class NextEntitiesCatalog implements EntitiesCatalog { From e9803242acc5d8ee555dc21eca8f0f7dabc44559 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Nov 2021 15:29:23 +0000 Subject: [PATCH 61/65] build(deps): bump passport-oauth2 from 1.6.0 to 1.6.1 Bumps [passport-oauth2](https://github.com/jaredhanson/passport-oauth2) from 1.6.0 to 1.6.1. - [Release notes](https://github.com/jaredhanson/passport-oauth2/releases) - [Changelog](https://github.com/jaredhanson/passport-oauth2/blob/master/CHANGELOG.md) - [Commits](https://github.com/jaredhanson/passport-oauth2/compare/v1.6.0...v1.6.1) --- updated-dependencies: - dependency-name: passport-oauth2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5c0a15392e..d318bcbb3d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22051,9 +22051,9 @@ passport-oauth2@1.2.0: uid2 "0.0.x" passport-oauth2@1.x.x, passport-oauth2@^1.1.2, passport-oauth2@^1.4.0, passport-oauth2@^1.5.0: - version "1.6.0" - resolved "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.6.0.tgz#5f599735e0ea40ea3027643785f81a3a9b4feb50" - integrity sha512-emXPLqLcVEcLFR/QvQXZcwLmfK8e9CqvMgmOFJxcNT3okSFMtUbRRKpY20x5euD+01uHsjjCa07DYboEeLXYiw== + version "1.6.1" + resolved "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.6.1.tgz#c5aee8f849ce8bd436c7f81d904a3cd1666f181b" + integrity sha512-ZbV43Hq9d/SBSYQ22GOiglFsjsD1YY/qdiptA+8ej+9C1dL1TVB+mBE5kDH/D4AJo50+2i8f4bx0vg4/yDDZCQ== dependencies: base64url "3.x.x" oauth "0.9.x" From 367e3bf130a5cfa9ee0c31de32d5440250636113 Mon Sep 17 00:00:00 2001 From: Jeremy Guarini Date: Mon, 8 Nov 2021 09:29:21 -0800 Subject: [PATCH 62/65] updated timestamp conversion to use Luxon and adrs-adr012 guideline, added luxon as dependency Signed-off-by: Jeremy Guarini --- plugins/code-coverage/package.json | 1 + .../CoverageHistoryChart/CoverageHistoryChart.tsx | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index 2b09b4d99f..8b6b4aef09 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -33,6 +33,7 @@ "@material-ui/lab": "4.0.0-alpha.57", "@material-ui/styles": "^4.11.0", "highlight.js": "^10.6.0", + "luxon": "^2.1.0", "react": "^16.13.1", "react-dom": "^16.13.1", "react-router": "6.0.0-beta.0", diff --git a/plugins/code-coverage/src/components/CoverageHistoryChart/CoverageHistoryChart.tsx b/plugins/code-coverage/src/components/CoverageHistoryChart/CoverageHistoryChart.tsx index a251bb1a68..4291599098 100644 --- a/plugins/code-coverage/src/components/CoverageHistoryChart/CoverageHistoryChart.tsx +++ b/plugins/code-coverage/src/components/CoverageHistoryChart/CoverageHistoryChart.tsx @@ -46,6 +46,8 @@ import { codeCoverageApiRef } from '../../api'; import { Progress, ResponseErrorPanel } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; +import { DateTime } from 'luxon'; + type Coverage = 'line' | 'branch'; const useStyles = makeStyles(theme => ({ @@ -71,7 +73,9 @@ const getTrendIcon = (trend: number, classes: ClassNameMap) => { // convert timestamp to human friendly form function formatDateToHuman(timeStamp: string | number) { - return new Date(timeStamp).toUTCString(); + return DateTime.fromMillis(Number(timeStamp)).toLocaleString( + DateTime.DATETIME_MED, + ); } export const CoverageHistoryChart = () => { From 564d392580b6d629788951cd458ec3bdd380960d Mon Sep 17 00:00:00 2001 From: Jeremy Guarini Date: Mon, 8 Nov 2021 09:40:10 -0800 Subject: [PATCH 63/65] Add Palo Alto Networks to Adopters page Signed-off-by: Jeremy Guarini --- ADOPTERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index 06501a2a10..f0b2fa16f6 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -63,3 +63,4 @@ | [Splunk](https://www.splunk.com) | [@tonytamsf](https://github.com/tonytamsf) | Developer portal as a centralized place to find people, services, documentation, escalation policies and give bravos. This portal is also being used as a centralized search engine for engineering specific documentation. | | [SoundCloud](https://www.soundcloud.com) | [Julio Zynger](https://github.com/julioz) | Developer portal as a [humane registry](https://martinfowler.com/bliki/HumaneRegistry.html) for the organization: catalog of people, services, documentation, feature toggles, escalation policies, etc. | | [Volvofinans Bank](https://www.volvofinans.se) | [Johan Hammar](https://github.com/johanhammar) | Developer portal enabling engineers to manage and explore software and documentation. | +| [Palo Alto Networks](https://www.paloaltonetworks.com) | [Jeremy Guarini](https://github.com/jeremyguarini), [Brian Lomeland](https://github.com/bbbmmmlll), [Palo Alto Networks](https://github.com/PaloAltoNetworks) | Developer portal, service catalog, documentation and tooling | From 1a915b49d44d04f73792d2c60f9820d5c98b26ba Mon Sep 17 00:00:00 2001 From: Jeremy Guarini Date: Mon, 8 Nov 2021 09:58:17 -0800 Subject: [PATCH 64/65] change luxon version to match others Signed-off-by: Jeremy Guarini --- plugins/code-coverage/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index 8b6b4aef09..d01e12dff5 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -33,7 +33,7 @@ "@material-ui/lab": "4.0.0-alpha.57", "@material-ui/styles": "^4.11.0", "highlight.js": "^10.6.0", - "luxon": "^2.1.0", + "luxon": "^2.0.2", "react": "^16.13.1", "react-dom": "^16.13.1", "react-router": "6.0.0-beta.0", From 0ef78fcd750df361f64d780a43df0c16b2837e51 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Nov 2021 18:09:15 +0000 Subject: [PATCH 65/65] build(deps): bump graphiql from 1.4.2 to 1.4.7 Bumps [graphiql](https://github.com/graphql/graphiql) from 1.4.2 to 1.4.7. - [Release notes](https://github.com/graphql/graphiql/releases) - [Changelog](https://github.com/graphql/graphiql/blob/main/CHANGELOG.md) - [Commits](https://github.com/graphql/graphiql/compare/graphiql@1.4.2...graphiql@1.4.7) --- updated-dependencies: - dependency-name: graphiql dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- yarn.lock | 134 +++++++++++++++++++++++++++++------------------------- 1 file changed, 73 insertions(+), 61 deletions(-) diff --git a/yarn.lock b/yarn.lock index 086ecc5f1a..b5786626dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2968,16 +2968,14 @@ stream-events "^1.0.1" xdg-basedir "^4.0.0" -"@graphiql/toolkit@^0.2.0": - version "0.2.2" - resolved "https://registry.npmjs.org/@graphiql/toolkit/-/toolkit-0.2.2.tgz#193d570afcf686c9ee61c92054c1782b9f3c1255" - integrity sha512-kDgYhqnS4p4LqSo1KvLd3tbX8Hhdj0ZrgQuGsosjjEnahiPYmmylxUL1p9lj6348OsypcTlCncGpEjeb9S3TiQ== +"@graphiql/toolkit@^0.3.2": + version "0.3.2" + resolved "https://registry.npmjs.org/@graphiql/toolkit/-/toolkit-0.3.2.tgz#551753436ada2bc27ea870b7668e5199a958ccfb" + integrity sha512-IweIT9VC8uDovg7kuCO9YqZcnIuWU8IGzrpUisXv6CUNK2Ed1ke8yERDTMmF/rjvLd2DeVZwM8iEOjEs4sUJQw== dependencies: - "@n1ru4l/push-pull-async-iterable-iterator" "^2.1.4" - graphql-ws "^4.3.2" + "@n1ru4l/push-pull-async-iterable-iterator" "^3.0.0" + graphql-ws "^4.9.0" meros "^1.1.4" - optionalDependencies: - subscriptions-transport-ws "^0.9.18" "@graphql-codegen/cli@^1.21.3": version "1.21.6" @@ -4635,10 +4633,10 @@ outvariant "^1.2.0" strict-event-emitter "^0.2.0" -"@n1ru4l/push-pull-async-iterable-iterator@^2.1.4": - version "2.1.4" - resolved "https://registry.npmjs.org/@n1ru4l/push-pull-async-iterable-iterator/-/push-pull-async-iterable-iterator-2.1.4.tgz#a90225474352f9f159bff979905f707b9c6bcf04" - integrity sha512-qLIvoOUJ+zritv+BlzcBMePKNjKQzH9Rb2i9W98YXxf/M62Lye8qH0peyiU8yJ1tL0kfulWi31BoK10E6BKJeA== +"@n1ru4l/push-pull-async-iterable-iterator@^3.0.0": + version "3.1.0" + resolved "https://registry.npmjs.org/@n1ru4l/push-pull-async-iterable-iterator/-/push-pull-async-iterable-iterator-3.1.0.tgz#be450c97d1c7cd6af1a992d53232704454345df9" + integrity sha512-K4scWxGhdQM0masHHy4gIQs2iGiLEXCrXttumknyPJqtdl4J179BjpibWSSQ1fxKdCcHgIlCTKXJU6cMM6D6Wg== "@nodelib/fs.scandir@2.1.3": version "2.1.3" @@ -11136,18 +11134,18 @@ code-point-at@^1.0.0: resolved "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= -codemirror-graphql@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/codemirror-graphql/-/codemirror-graphql-1.0.0.tgz#ba8db60dc42b87768d643b3d19bf088f43dc5380" - integrity sha512-6LnSeRldL7psIBfjDr4xXKxCqPVYfQE4Yj04p2VpIyAIpc4MVE4VOjzvILgnmAW8X93ou5/s5gQXvB4huDwTUQ== +codemirror-graphql@^1.0.3: + version "1.1.0" + resolved "https://registry.npmjs.org/codemirror-graphql/-/codemirror-graphql-1.1.0.tgz#dd22ddf7761efa9131fa99a70a4a85fe653484e5" + integrity sha512-bp2XUg7epL07kJcylM8VCISK6X+rFsHL2lUkPQAw2v721MVhn+80FgjMP8tiZCOfJgHn1+JgsA71L5nOHWgUdA== dependencies: - graphql-language-service-interface "^2.8.2" - graphql-language-service-parser "^1.9.0" + graphql-language-service-interface "^2.9.0" + graphql-language-service-parser "^1.10.0" -codemirror@^5.54.0: - version "5.59.4" - resolved "https://registry.npmjs.org/codemirror/-/codemirror-5.59.4.tgz#bfc11c8ce32b04818e8d661bbd790a94f4b3a6f3" - integrity sha512-achw5JBgx8QPcACDDn+EUUXmCYzx/zxEtOGXyjvLEvYY8GleUrnfm5D+Zb+UjShHggXKDT9AXrbkBZX6a0YSQg== +codemirror@^5.58.2: + version "5.63.3" + resolved "https://registry.npmjs.org/codemirror/-/codemirror-5.63.3.tgz#97042a242027fe0c87c09b36bc01931d37b76527" + integrity sha512-1C+LELr+5grgJYqwZKqxrcbPsHFHapVaVAloBsFBASbpLnQqLw1U8yXJ3gT5D+rhxIiSpo+kTqN+hQ+9ialIXw== codeowners-utils@^1.0.2: version "1.0.2" @@ -13418,7 +13416,12 @@ entities@^1.1.1, entities@^1.1.2: resolved "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== -entities@^2.0.0, entities@~2.0.0: +entities@^2.0.0, entities@~2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" + integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== + +entities@~2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== @@ -15541,18 +15544,19 @@ grapheme-splitter@^1.0.4: integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== graphiql@^1.0.0-alpha.10: - version "1.4.2" - resolved "https://registry.npmjs.org/graphiql/-/graphiql-1.4.2.tgz#a1dc1a4d8d35f60c90d6d8a9eb62a99756e9fd9b" - integrity sha512-TQDuuU/ZqTWV1yQDpVEiKskg0IYA+Wck37DYrrFzLlpgZWRbWiyab1PyHKiRep7J540CgScBg6C/gGCymKyO3g== + version "1.4.7" + resolved "https://registry.npmjs.org/graphiql/-/graphiql-1.4.7.tgz#6a35acf0786d7518fbb986b75bf0a3d752c19c1a" + integrity sha512-oHsBTzdWTbRJhqazbjrC6wY7YInViErAeXLqetCxdFFu2Zk5FV3V3rs7KPrCyr7kM6lW0nfXMzIfKuIgxAqx7g== dependencies: - "@graphiql/toolkit" "^0.2.0" - codemirror "^5.54.0" - codemirror-graphql "^1.0.0" + "@graphiql/toolkit" "^0.3.2" + codemirror "^5.58.2" + codemirror-graphql "^1.0.3" copy-to-clipboard "^3.2.0" dset "^3.1.0" entities "^2.0.0" - graphql-language-service "^3.1.2" - markdown-it "^10.0.0" + escape-html "^1.0.3" + graphql-language-service "^3.1.6" + markdown-it "^12.2.0" graphlib@^2.1.8: version "2.1.8" @@ -15587,7 +15591,7 @@ graphql-extensions@^0.15.0: apollo-server-env "^3.1.0" apollo-server-types "^0.9.0" -graphql-language-service-interface@2.8.2, graphql-language-service-interface@^2.8.2: +graphql-language-service-interface@2.8.2, graphql-language-service-interface@^2.9.0: version "2.8.2" resolved "https://registry.npmjs.org/graphql-language-service-interface/-/graphql-language-service-interface-2.8.2.tgz#b3bb2aef7eaf0dff0b4ea419fa412c5f66fa268b" integrity sha512-otbOQmhgkAJU1QJgQkMztNku6SbJLu/uodoFOYOOtJsizTjrMs93vkYaHCcYnLA3oi1Goj27XcHjMnRCYQOZXQ== @@ -15597,7 +15601,7 @@ graphql-language-service-interface@2.8.2, graphql-language-service-interface@^2. graphql-language-service-utils "^2.5.1" vscode-languageserver-types "^3.15.1" -graphql-language-service-parser@1.9.0, graphql-language-service-parser@^1.9.0: +graphql-language-service-parser@1.9.0, graphql-language-service-parser@^1.10.0, graphql-language-service-parser@^1.9.0: version "1.9.0" resolved "https://registry.npmjs.org/graphql-language-service-parser/-/graphql-language-service-parser-1.9.0.tgz#79af21294119a0a7e81b6b994a1af36833bab724" integrity sha512-B5xPZLbBmIp0kHvpY1Z35I5DtPoDK9wGxQVRDIzcBaiIvAmlTrDvjo3bu7vKREdjFbYKvWNgrEWENuprMbF17Q== @@ -15609,6 +15613,11 @@ graphql-language-service-types@^1.8.0: resolved "https://registry.npmjs.org/graphql-language-service-types/-/graphql-language-service-types-1.8.1.tgz#963810010924f2b5eaea415d5b8eb0b7d42c479b" integrity sha512-IpYS0mEHEmRsFlq+loWCpSYYYizAID7Alri6GoFN1QqUdux+8rp1Tkp2NGsGDpDmm3Dbz5ojmJWzNWQGpuwveA== +graphql-language-service-types@^1.8.2: + version "1.8.2" + resolved "https://registry.npmjs.org/graphql-language-service-types/-/graphql-language-service-types-1.8.2.tgz#50ae56f69cc24fcfc3daa129b68b0eb9421e8578" + integrity sha512-Sj07RHnMwAhEvAt7Jdt1l/x56ZpoNh+V6g+T58CF6GiYqI5l4vXqqRB4d4xHDcNQX98GpJfnf3o8BqPgP3C5Sw== + graphql-language-service-utils@^2.5.1: version "2.5.1" resolved "https://registry.npmjs.org/graphql-language-service-utils/-/graphql-language-service-utils-2.5.1.tgz#832ad4b0a9da03fdded756932c27e057ccf71302" @@ -15617,13 +15626,23 @@ graphql-language-service-utils@^2.5.1: graphql-language-service-types "^1.8.0" nullthrows "^1.0.0" -graphql-language-service@^3.1.2: - version "3.1.2" - resolved "https://registry.npmjs.org/graphql-language-service/-/graphql-language-service-3.1.2.tgz#6f50d5d824ea09c402cb02902b10e54b9da899d5" - integrity sha512-OiOH8mVE+uotrl3jGA2Pgt9k7rrI8lgw/8p+Cf6nwyEHbmIZj37vX9KoOWgpdFhuQlw824nNxWHSbz6k90xjWQ== +graphql-language-service-utils@^2.5.3: + version "2.5.3" + resolved "https://registry.npmjs.org/graphql-language-service-utils/-/graphql-language-service-utils-2.5.3.tgz#185f4f65cf8c010871eb9405452a3a0bfdf88748" + integrity sha512-ydevEZ0AgzEKQF3hiCbLXuS0o7189Ww/T30WtCKCLaRHDYk9Yyb2PZWdhSTWLxYZTaX2TccV6NtFWvzIC7UP3g== dependencies: - graphql-language-service-interface "^2.8.2" graphql-language-service-types "^1.8.0" + nullthrows "^1.0.0" + +graphql-language-service@^3.1.6: + version "3.2.0" + resolved "https://registry.npmjs.org/graphql-language-service/-/graphql-language-service-3.2.0.tgz#e0eb6d5dea2cab92549a253d7a6b4fa0cce178b7" + integrity sha512-xM5Ua5p7ttG/oEaDy2zk35FP2O2I9qD2N0DOrjCDUVDRC06FNDG+/CvF4qX9+i8DWOI65xch5vAhSQEfS2jFsA== + dependencies: + graphql-language-service-interface "^2.9.0" + graphql-language-service-parser "^1.10.0" + graphql-language-service-types "^1.8.2" + graphql-language-service-utils "^2.5.3" graphql-request@^3.3.0: version "3.4.0" @@ -15678,16 +15697,16 @@ graphql-type-json@^0.3.2: resolved "https://registry.npmjs.org/graphql-type-json/-/graphql-type-json-0.3.2.tgz#f53a851dbfe07bd1c8157d24150064baab41e115" integrity sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg== -graphql-ws@^4.3.2: - version "4.9.0" - resolved "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.9.0.tgz#5cfd8bb490b35e86583d8322f5d5d099c26e365c" - integrity sha512-sHkK9+lUm20/BGawNEWNtVAeJzhZeBg21VmvmLoT5NdGVeZWv5PdIhkcayQIAgjSyyQ17WMKmbDijIPG2On+Ag== - graphql-ws@^4.4.1: version "4.7.0" resolved "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.7.0.tgz#b323fbf35a3736eed85dac24c0054d6d10c93e62" integrity sha512-Md8SsmC9ZlsogFPd3Ot8HbIAAqsHh8Xoq7j4AmcIat1Bh6k91tjVyQvA0Au1/BolXSYq+RDvib6rATU2Hcf1Xw== +graphql-ws@^4.9.0: + version "4.9.0" + resolved "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.9.0.tgz#5cfd8bb490b35e86583d8322f5d5d099c26e365c" + integrity sha512-sHkK9+lUm20/BGawNEWNtVAeJzhZeBg21VmvmLoT5NdGVeZWv5PdIhkcayQIAgjSyyQ17WMKmbDijIPG2On+Ag== + graphql@15.5.0: version "15.5.0" resolved "https://registry.npmjs.org/graphql/-/graphql-15.5.0.tgz#39d19494dbe69d1ea719915b578bf920344a69d5" @@ -18816,13 +18835,6 @@ lines-and-columns@^1.1.6: resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -linkify-it@^2.0.0: - version "2.2.0" - resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz#e3b54697e78bf915c70a38acd78fd09e0058b1cf" - integrity sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw== - dependencies: - uc.micro "^1.0.1" - linkify-it@^3.0.1: version "3.0.2" resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.2.tgz#f55eeb8bc1d3ae754049e124ab3bb56d97797fb8" @@ -19518,17 +19530,6 @@ markdown-escapes@^1.0.0: resolved "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== -markdown-it@^10.0.0: - version "10.0.0" - resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc" - integrity sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg== - dependencies: - argparse "^1.0.7" - entities "~2.0.0" - linkify-it "^2.0.0" - mdurl "^1.0.1" - uc.micro "^1.0.5" - markdown-it@^11.0.1: version "11.0.1" resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-11.0.1.tgz#b54f15ec2a2193efa66dda1eb4173baea08993d6" @@ -19540,6 +19541,17 @@ markdown-it@^11.0.1: mdurl "^1.0.1" uc.micro "^1.0.5" +markdown-it@^12.2.0: + version "12.2.0" + resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-12.2.0.tgz#091f720fd5db206f80de7a8d1f1a7035fd0d38db" + integrity sha512-Wjws+uCrVQRqOoJvze4HCqkKl1AsSh95iFAeQDwnyfxM09divCBSXlDR1uTvyUP3Grzpn4Ru8GeCxYPM8vkCQg== + dependencies: + argparse "^2.0.1" + entities "~2.1.0" + linkify-it "^3.0.1" + mdurl "^1.0.1" + uc.micro "^1.0.5" + markdown-table@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz#194a90ced26d31fe753d8b9434430214c011865b"