From f32252cdf631378a398d4afbd0d785c53535fe3a Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Wed, 26 Apr 2023 19:55:05 +0100 Subject: [PATCH 01/64] feat(catalog-backend): Add observability for catalog processing Signed-off-by: Mike Bryant Signed-off-by: Mike Bryant --- .changeset/slimy-kids-jam.md | 5 + .../DefaultCatalogProcessingEngine.ts | 320 +++++++++--------- .../DefaultCatalogProcessingOrchestrator.ts | 90 +++-- .../catalog-backend/src/util/opentelemetry.ts | 32 ++ 4 files changed, 266 insertions(+), 181 deletions(-) create mode 100644 .changeset/slimy-kids-jam.md create mode 100644 plugins/catalog-backend/src/util/opentelemetry.ts diff --git a/.changeset/slimy-kids-jam.md b/.changeset/slimy-kids-jam.md new file mode 100644 index 0000000000..ea2897bed1 --- /dev/null +++ b/.changeset/slimy-kids-jam.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Added OpenTelemetry spans for catalog processing diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts index 8e2f8b2f6f..e9f87c36c7 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts @@ -23,7 +23,7 @@ import { assertError, serializeError, stringifyError } from '@backstage/errors'; import { Hash } from 'crypto'; import stableStringify from 'fast-json-stable-stringify'; import { Logger } from 'winston'; -import { metrics } from '@opentelemetry/api'; +import { metrics, SpanStatusCode, trace } from '@opentelemetry/api'; import { ProcessingDatabase, RefreshStateItem } from '../database/types'; import { createCounterMetric, createSummaryMetric } from '../util/metrics'; import { @@ -35,9 +35,12 @@ import { Stitcher } from '../stitching/Stitcher'; import { startTaskPipeline } from './TaskPipeline'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; +import { addEntityAttributes, TRACER_ID } from '../util/opentelemetry'; const CACHE_TTL = 5; +const tracer = trace.getTracer(TRACER_ID); + export type ProgressTracker = ReturnType; export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { @@ -131,177 +134,186 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { } }, processTask: async item => { - const track = this.tracker.processStart(item, this.logger); + await tracer.startActiveSpan('ProcessingRun', async span => { + const track = this.tracker.processStart(item, this.logger); + addEntityAttributes(span, item.entityRef); - try { - const { - id, - state, - unprocessedEntity, - entityRef, - locationKey, - resultHash: previousResultHash, - } = item; - const result = await this.orchestrator.process({ - entity: unprocessedEntity, - state, - }); + try { + const { + id, + state, + unprocessedEntity, + entityRef, + locationKey, + resultHash: previousResultHash, + } = item; + const result = await this.orchestrator.process({ + entity: unprocessedEntity, + state, + }); - track.markProcessorsCompleted(result); + track.markProcessorsCompleted(result); - if (result.ok) { - const { ttl: _, ...stateWithoutTtl } = state ?? {}; - if ( - stableStringify(stateWithoutTtl) !== stableStringify(result.state) - ) { + if (result.ok) { + const { ttl: _, ...stateWithoutTtl } = state ?? {}; + if ( + stableStringify(stateWithoutTtl) !== stableStringify(result.state) + ) { + await this.processingDatabase.transaction(async tx => { + await this.processingDatabase.updateEntityCache(tx, { + id, + state: { + ttl: CACHE_TTL, + ...result.state, + }, + }); + }); + } + } else { + const maybeTtl = state?.ttl; + const ttl = Number.isInteger(maybeTtl) ? (maybeTtl as number) : 0; await this.processingDatabase.transaction(async tx => { await this.processingDatabase.updateEntityCache(tx, { id, - state: { - ttl: CACHE_TTL, - ...result.state, - }, + state: ttl > 0 ? { ...state, ttl: ttl - 1 } : {}, }); }); } - } else { - const maybeTtl = state?.ttl; - const ttl = Number.isInteger(maybeTtl) ? (maybeTtl as number) : 0; - await this.processingDatabase.transaction(async tx => { - await this.processingDatabase.updateEntityCache(tx, { - id, - state: ttl > 0 ? { ...state, ttl: ttl - 1 } : {}, + + const location = + unprocessedEntity?.metadata?.annotations?.[ANNOTATION_LOCATION]; + for (const error of result.errors) { + this.logger.warn(error.message, { + entity: entityRef, + location, }); - }); - } + } + const errorsString = JSON.stringify( + result.errors.map(e => serializeError(e)), + ); - const location = - unprocessedEntity?.metadata?.annotations?.[ANNOTATION_LOCATION]; - for (const error of result.errors) { - this.logger.warn(error.message, { - entity: entityRef, - location, - }); - } - const errorsString = JSON.stringify( - result.errors.map(e => serializeError(e)), - ); + let hashBuilder = this.createHash().update(errorsString); - let hashBuilder = this.createHash().update(errorsString); - - if (result.ok) { - const { entityRefs: parents } = - await this.processingDatabase.transaction(tx => - this.processingDatabase.listParents(tx, { - entityRef, - }), - ); - - hashBuilder = hashBuilder - .update(stableStringify({ ...result.completedEntity })) - .update(stableStringify([...result.deferredEntities])) - .update(stableStringify([...result.relations])) - .update(stableStringify([...result.refreshKeys])) - .update(stableStringify([...parents])); - } - - const resultHash = hashBuilder.digest('hex'); - if (resultHash === previousResultHash) { - // If nothing changed in our produced outputs, we cannot have any - // significant effect on our surroundings; therefore, we just abort - // without any updates / stitching. - track.markSuccessfulWithNoChanges(); - return; - } - - // If the result was marked as not OK, it signals that some part of the - // processing pipeline threw an exception. This can happen both as part of - // non-catastrophic things such as due to validation errors, as well as if - // something fatal happens inside the processing for other reasons. In any - // case, this means we can't trust that anything in the output is okay. So - // just store the errors and trigger a stich so that they become visible to - // the outside. - if (!result.ok) { - // notify the error listener if the entity can not be processed. - Promise.resolve(undefined) - .then(() => - this.onProcessingError?.({ - unprocessedEntity, - errors: result.errors, - }), - ) - .catch(error => { - this.logger.debug( - `Processing error listener threw an exception, ${stringifyError( - error, - )}`, + if (result.ok) { + const { entityRefs: parents } = + await this.processingDatabase.transaction(tx => + this.processingDatabase.listParents(tx, { + entityRef, + }), ); - }); + hashBuilder = hashBuilder + .update(stableStringify({ ...result.completedEntity })) + .update(stableStringify([...result.deferredEntities])) + .update(stableStringify([...result.relations])) + .update(stableStringify([...result.refreshKeys])) + .update(stableStringify([...parents])); + } + + const resultHash = hashBuilder.digest('hex'); + if (resultHash === previousResultHash) { + // If nothing changed in our produced outputs, we cannot have any + // significant effect on our surroundings; therefore, we just abort + // without any updates / stitching. + track.markSuccessfulWithNoChanges(); + span.end(); + return; + } + + // If the result was marked as not OK, it signals that some part of the + // processing pipeline threw an exception. This can happen both as part of + // non-catastrophic things such as due to validation errors, as well as if + // something fatal happens inside the processing for other reasons. In any + // case, this means we can't trust that anything in the output is okay. So + // just store the errors and trigger a stich so that they become visible to + // the outside. + if (!result.ok) { + // notify the error listener if the entity can not be processed. + Promise.resolve(undefined) + .then(() => + this.onProcessingError?.({ + unprocessedEntity, + errors: result.errors, + }), + ) + .catch(error => { + this.logger.debug( + `Processing error listener threw an exception, ${stringifyError( + error, + )}`, + ); + }); + + await this.processingDatabase.transaction(async tx => { + await this.processingDatabase.updateProcessedEntityErrors(tx, { + id, + errors: errorsString, + resultHash, + }); + }); + await this.stitcher.stitch( + new Set([stringifyEntityRef(unprocessedEntity)]), + ); + track.markSuccessfulWithErrors(); + span.setStatus({ code: SpanStatusCode.ERROR }); + span.end(); + return; + } + + result.completedEntity.metadata.uid = id; + let oldRelationSources: Map; await this.processingDatabase.transaction(async tx => { - await this.processingDatabase.updateProcessedEntityErrors(tx, { - id, - errors: errorsString, - resultHash, - }); + const { previous } = + await this.processingDatabase.updateProcessedEntity(tx, { + id, + processedEntity: result.completedEntity, + resultHash, + errors: errorsString, + relations: result.relations, + deferredEntities: result.deferredEntities, + locationKey, + refreshKeys: result.refreshKeys, + }); + oldRelationSources = new Map( + previous.relations.map(r => [ + `${r.source_entity_ref}:${r.type}`, + r.source_entity_ref, + ]), + ); }); - await this.stitcher.stitch( - new Set([stringifyEntityRef(unprocessedEntity)]), + + const newRelationSources = new Map( + result.relations.map(relation => { + const sourceEntityRef = stringifyEntityRef(relation.source); + return [`${sourceEntityRef}:${relation.type}`, sourceEntityRef]; + }), ); - track.markSuccessfulWithErrors(); - return; + + const setOfThingsToStitch = new Set([ + stringifyEntityRef(result.completedEntity), + ]); + newRelationSources.forEach((sourceEntityRef, uniqueKey) => { + if (!oldRelationSources.has(uniqueKey)) { + setOfThingsToStitch.add(sourceEntityRef); + } + }); + oldRelationSources!.forEach((sourceEntityRef, uniqueKey) => { + if (!newRelationSources.has(uniqueKey)) { + setOfThingsToStitch.add(sourceEntityRef); + } + }); + + await this.stitcher.stitch(setOfThingsToStitch); + + track.markSuccessfulWithChanges(setOfThingsToStitch.size); + } catch (error) { + assertError(error); + track.markFailed(error); + span.recordException(error); + span.setStatus({ code: SpanStatusCode.ERROR }); } - - result.completedEntity.metadata.uid = id; - let oldRelationSources: Map; - await this.processingDatabase.transaction(async tx => { - const { previous } = - await this.processingDatabase.updateProcessedEntity(tx, { - id, - processedEntity: result.completedEntity, - resultHash, - errors: errorsString, - relations: result.relations, - deferredEntities: result.deferredEntities, - locationKey, - refreshKeys: result.refreshKeys, - }); - oldRelationSources = new Map( - previous.relations.map(r => [ - `${r.source_entity_ref}:${r.type}`, - r.source_entity_ref, - ]), - ); - }); - - const newRelationSources = new Map( - result.relations.map(relation => { - const sourceEntityRef = stringifyEntityRef(relation.source); - return [`${sourceEntityRef}:${relation.type}`, sourceEntityRef]; - }), - ); - - const setOfThingsToStitch = new Set([ - stringifyEntityRef(result.completedEntity), - ]); - newRelationSources.forEach((sourceEntityRef, uniqueKey) => { - if (!oldRelationSources.has(uniqueKey)) { - setOfThingsToStitch.add(sourceEntityRef); - } - }); - oldRelationSources!.forEach((sourceEntityRef, uniqueKey) => { - if (!newRelationSources.has(uniqueKey)) { - setOfThingsToStitch.add(sourceEntityRef); - } - }); - - await this.stitcher.stitch(setOfThingsToStitch); - - track.markSuccessfulWithChanges(setOfThingsToStitch.size); - } catch (error) { - assertError(error); - track.markFailed(error); - } + span.end(); + }); }, }); } diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts index d177a7d4a8..2b4102677e 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { Span, SpanStatusCode, trace } from '@opentelemetry/api'; import { Entity, EntityPolicy, @@ -55,6 +56,9 @@ import { } from './util'; import { CatalogRulesEnforcer } from '../ingestion/CatalogRules'; import { ProcessorCacheManager } from './ProcessorCacheManager'; +import { addEntityAttributes, TRACER_ID } from '../util/opentelemetry'; + +const tracer = trace.getTracer(TRACER_ID); type Context = { entityRef: string; @@ -64,6 +68,18 @@ type Context = { cache: ProcessorCacheManager; }; +function addProcessorAttributes( + span: Span, + stage: string, + processor: CatalogProcessor, +) { + span.setAttribute('backstage.catalog.processor.stage', stage); + span.setAttribute( + 'backstage.catalog.processor.name', + processor.getProcessorName(), + ); +} + /** @public */ export class DefaultCatalogProcessingOrchestrator implements CatalogProcessingOrchestrator @@ -183,20 +199,30 @@ export class DefaultCatalogProcessingOrchestrator for (const processor of this.options.processors) { if (processor.preProcessEntity) { - try { - res = await processor.preProcessEntity( - res, - context.location, - context.collector.forProcessor(processor), - context.originLocation, - context.cache.forProcessor(processor), - ); - } catch (e) { - throw new InputError( - `Processor ${processor.constructor.name} threw an error while preprocessing`, - e, - ); - } + let innerRes = res; + res = await tracer.startActiveSpan('ProcessingStep', async span => { + addEntityAttributes(span, context.entityRef); + addProcessorAttributes(span, 'preProcessEntity', processor); + try { + innerRes = await processor.preProcessEntity!( + innerRes, + context.location, + context.collector.forProcessor(processor), + context.originLocation, + context.cache.forProcessor(processor), + ); + } catch (e) { + span.recordException(e); + span.setStatus({ code: SpanStatusCode.ERROR }); + span.end(); + throw new InputError( + `Processor ${processor.constructor.name} threw an error while preprocessing`, + e, + ); + } + span.end(); + return innerRes; + }); } } @@ -361,19 +387,29 @@ export class DefaultCatalogProcessingOrchestrator for (const processor of this.options.processors) { if (processor.postProcessEntity) { - try { - res = await processor.postProcessEntity( - res, - context.location, - context.collector.forProcessor(processor), - context.cache.forProcessor(processor), - ); - } catch (e) { - throw new InputError( - `Processor ${processor.constructor.name} threw an error while postprocessing`, - e, - ); - } + let innerRes = res; + res = await tracer.startActiveSpan('ProcessingStep', async span => { + addEntityAttributes(span, context.entityRef); + addProcessorAttributes(span, 'postProcessEntity', processor); + try { + innerRes = await processor.postProcessEntity!( + innerRes, + context.location, + context.collector.forProcessor(processor), + context.cache.forProcessor(processor), + ); + } catch (e) { + span.recordException(e); + span.setStatus({ code: SpanStatusCode.ERROR }); + span.end(); + throw new InputError( + `Processor ${processor.constructor.name} threw an error while postprocessing`, + e, + ); + } + span.end(); + return innerRes; + }); } } diff --git a/plugins/catalog-backend/src/util/opentelemetry.ts b/plugins/catalog-backend/src/util/opentelemetry.ts new file mode 100644 index 0000000000..16ec276a79 --- /dev/null +++ b/plugins/catalog-backend/src/util/opentelemetry.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Span, SpanStatusCode } from '@opentelemetry/api'; +import { parseEntityRef } from '@backstage/catalog-model'; + +export const TRACER_ID = 'backstage-plugin-catalog-backend'; + +export function addEntityAttributes(span: Span, entityRef: string) { + try { + const fields = parseEntityRef(entityRef); + span.setAttribute('backstage.entity.kind', fields.kind); + span.setAttribute('backstage.entity.namespace', fields.namespace); + span.setAttribute('backstage.entity.name', fields.name); + } catch (err) { + span.recordException(err); + span.setStatus({ code: SpanStatusCode.ERROR }); + } +} From c1d8f44180bf5a30eaff492d8b1f8ecf97d31241 Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Thu, 18 May 2023 21:01:35 +0100 Subject: [PATCH 02/64] refactor: Pull out withActiveSpan to ensure we always end the span regardless of exception Signed-off-by: Mike Bryant --- .../DefaultCatalogProcessingEngine.ts | 19 ++++---- .../DefaultCatalogProcessingOrchestrator.ts | 18 +++---- .../catalog-backend/src/util/opentelemetry.ts | 47 ++++++++++++++++++- 3 files changed, 62 insertions(+), 22 deletions(-) diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts index e9f87c36c7..2ae104666f 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts @@ -23,7 +23,7 @@ import { assertError, serializeError, stringifyError } from '@backstage/errors'; import { Hash } from 'crypto'; import stableStringify from 'fast-json-stable-stringify'; import { Logger } from 'winston'; -import { metrics, SpanStatusCode, trace } from '@opentelemetry/api'; +import { metrics, trace } from '@opentelemetry/api'; import { ProcessingDatabase, RefreshStateItem } from '../database/types'; import { createCounterMetric, createSummaryMetric } from '../util/metrics'; import { @@ -35,7 +35,11 @@ import { Stitcher } from '../stitching/Stitcher'; import { startTaskPipeline } from './TaskPipeline'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; -import { addEntityAttributes, TRACER_ID } from '../util/opentelemetry'; +import { + addEntityAttributes, + TRACER_ID, + withActiveSpan, +} from '../util/opentelemetry'; const CACHE_TTL = 5; @@ -134,7 +138,7 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { } }, processTask: async item => { - await tracer.startActiveSpan('ProcessingRun', async span => { + await withActiveSpan(tracer, 'ProcessingRun', async span => { const track = this.tracker.processStart(item, this.logger); addEntityAttributes(span, item.entityRef); @@ -157,7 +161,8 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { if (result.ok) { const { ttl: _, ...stateWithoutTtl } = state ?? {}; if ( - stableStringify(stateWithoutTtl) !== stableStringify(result.state) + stableStringify(stateWithoutTtl) !== + stableStringify(result.state) ) { await this.processingDatabase.transaction(async tx => { await this.processingDatabase.updateEntityCache(tx, { @@ -216,7 +221,6 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { // significant effect on our surroundings; therefore, we just abort // without any updates / stitching. track.markSuccessfulWithNoChanges(); - span.end(); return; } @@ -255,8 +259,6 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { new Set([stringifyEntityRef(unprocessedEntity)]), ); track.markSuccessfulWithErrors(); - span.setStatus({ code: SpanStatusCode.ERROR }); - span.end(); return; } @@ -309,10 +311,7 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { } catch (error) { assertError(error); track.markFailed(error); - span.recordException(error); - span.setStatus({ code: SpanStatusCode.ERROR }); } - span.end(); }); }, }); diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts index 2b4102677e..8091c6f120 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts @@ -56,7 +56,11 @@ import { } from './util'; import { CatalogRulesEnforcer } from '../ingestion/CatalogRules'; import { ProcessorCacheManager } from './ProcessorCacheManager'; -import { addEntityAttributes, TRACER_ID } from '../util/opentelemetry'; +import { + addEntityAttributes, + TRACER_ID, + withActiveSpan, +} from '../util/opentelemetry'; const tracer = trace.getTracer(TRACER_ID); @@ -200,7 +204,7 @@ export class DefaultCatalogProcessingOrchestrator for (const processor of this.options.processors) { if (processor.preProcessEntity) { let innerRes = res; - res = await tracer.startActiveSpan('ProcessingStep', async span => { + res = await withActiveSpan(tracer, 'ProcessingStep', async span => { addEntityAttributes(span, context.entityRef); addProcessorAttributes(span, 'preProcessEntity', processor); try { @@ -212,15 +216,11 @@ export class DefaultCatalogProcessingOrchestrator context.cache.forProcessor(processor), ); } catch (e) { - span.recordException(e); - span.setStatus({ code: SpanStatusCode.ERROR }); - span.end(); throw new InputError( `Processor ${processor.constructor.name} threw an error while preprocessing`, e, ); } - span.end(); return innerRes; }); } @@ -388,7 +388,7 @@ export class DefaultCatalogProcessingOrchestrator for (const processor of this.options.processors) { if (processor.postProcessEntity) { let innerRes = res; - res = await tracer.startActiveSpan('ProcessingStep', async span => { + res = await withActiveSpan(tracer, 'ProcessingStep', async span => { addEntityAttributes(span, context.entityRef); addProcessorAttributes(span, 'postProcessEntity', processor); try { @@ -399,15 +399,11 @@ export class DefaultCatalogProcessingOrchestrator context.cache.forProcessor(processor), ); } catch (e) { - span.recordException(e); - span.setStatus({ code: SpanStatusCode.ERROR }); - span.end(); throw new InputError( `Processor ${processor.constructor.name} threw an error while postprocessing`, e, ); } - span.end(); return innerRes; }); } diff --git a/plugins/catalog-backend/src/util/opentelemetry.ts b/plugins/catalog-backend/src/util/opentelemetry.ts index 16ec276a79..391869deb8 100644 --- a/plugins/catalog-backend/src/util/opentelemetry.ts +++ b/plugins/catalog-backend/src/util/opentelemetry.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Span, SpanStatusCode } from '@opentelemetry/api'; +import { Span, SpanOptions, SpanStatusCode, Tracer } from '@opentelemetry/api'; import { parseEntityRef } from '@backstage/catalog-model'; export const TRACER_ID = 'backstage-plugin-catalog-backend'; @@ -30,3 +30,48 @@ export function addEntityAttributes(span: Span, entityRef: string) { span.setStatus({ code: SpanStatusCode.ERROR }); } } + +// Adapted from https://github.com/open-telemetry/opentelemetry-js/blob/359fbcc40a859057a02b14e84599eac399b8dba7/api/src/trace/SugaredTracer.ts +// While waiting for something like https://github.com/open-telemetry/opentelemetry-js/pull/3317 to land upstream + +const onException = (e: Error, span: Span) => { + span.recordException(e); + span.setStatus({ + code: SpanStatusCode.ERROR, + }); +}; + +function handleFn ReturnType>( + span: Span, + fn: F, +): ReturnType { + try { + const ret = fn(span) as Promise>; + // if fn is an async function attach a recordException and spanEnd callback to the promise + if (typeof ret.then === 'function' && typeof ret.catch === 'function') { + return ret + .catch((e: Error) => { + onException(e, span); + throw e; + }) + .finally(() => span.end()) as ReturnType; + } + span.end(); + return ret as ReturnType; + } catch (e) { + onException(e, span); + span.end(); + throw e; + } +} + +export function withActiveSpan ReturnType>( + tracer: Tracer, + name: string, + fn: F, + spanOptions: SpanOptions = {}, +): ReturnType { + return tracer.startActiveSpan(name, spanOptions, (span: Span) => { + return handleFn(span, fn); + }); +} From db2cac67450cd75c95ce1ed761b2c6f0993f7bde Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Thu, 18 May 2023 21:06:51 +0100 Subject: [PATCH 03/64] feat: Add ProcessingStage span Signed-off-by: Mike Bryant --- .../DefaultCatalogProcessingOrchestrator.ts | 112 ++++++++++-------- 1 file changed, 63 insertions(+), 49 deletions(-) diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts index 8091c6f120..8b4f4127f3 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts @@ -199,34 +199,41 @@ export class DefaultCatalogProcessingOrchestrator entity: Entity, context: Context, ): Promise { - let res = entity; + return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => { + addEntityAttributes(stageSpan, context.entityRef); + stageSpan.setAttribute( + 'backstage.catalog.processor.stage', + 'preProcessEntity', + ); + let res = entity; - for (const processor of this.options.processors) { - if (processor.preProcessEntity) { - let innerRes = res; - res = await withActiveSpan(tracer, 'ProcessingStep', async span => { - addEntityAttributes(span, context.entityRef); - addProcessorAttributes(span, 'preProcessEntity', processor); - try { - innerRes = await processor.preProcessEntity!( - innerRes, - context.location, - context.collector.forProcessor(processor), - context.originLocation, - context.cache.forProcessor(processor), - ); - } catch (e) { - throw new InputError( - `Processor ${processor.constructor.name} threw an error while preprocessing`, - e, - ); - } - return innerRes; - }); + for (const processor of this.options.processors) { + if (processor.preProcessEntity) { + let innerRes = res; + res = await withActiveSpan(tracer, 'ProcessingStep', async span => { + addEntityAttributes(span, context.entityRef); + addProcessorAttributes(span, 'preProcessEntity', processor); + try { + innerRes = await processor.preProcessEntity!( + innerRes, + context.location, + context.collector.forProcessor(processor), + context.originLocation, + context.cache.forProcessor(processor), + ); + } catch (e) { + throw new InputError( + `Processor ${processor.constructor.name} threw an error while preprocessing`, + e, + ); + } + return innerRes; + }); + } } - } - return res; + return res; + }); } /** @@ -383,32 +390,39 @@ export class DefaultCatalogProcessingOrchestrator entity: Entity, context: Context, ): Promise { - let res = entity; + return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => { + addEntityAttributes(stageSpan, context.entityRef); + stageSpan.setAttribute( + 'backstage.catalog.processor.stage', + 'postProcessEntity', + ); + let res = entity; - for (const processor of this.options.processors) { - if (processor.postProcessEntity) { - let innerRes = res; - res = await withActiveSpan(tracer, 'ProcessingStep', async span => { - addEntityAttributes(span, context.entityRef); - addProcessorAttributes(span, 'postProcessEntity', processor); - try { - innerRes = await processor.postProcessEntity!( - innerRes, - context.location, - context.collector.forProcessor(processor), - context.cache.forProcessor(processor), - ); - } catch (e) { - throw new InputError( - `Processor ${processor.constructor.name} threw an error while postprocessing`, - e, - ); - } - return innerRes; - }); + for (const processor of this.options.processors) { + if (processor.postProcessEntity) { + let innerRes = res; + res = await withActiveSpan(tracer, 'ProcessingStep', async span => { + addEntityAttributes(span, context.entityRef); + addProcessorAttributes(span, 'postProcessEntity', processor); + try { + innerRes = await processor.postProcessEntity!( + innerRes, + context.location, + context.collector.forProcessor(processor), + context.cache.forProcessor(processor), + ); + } catch (e) { + throw new InputError( + `Processor ${processor.constructor.name} threw an error while postprocessing`, + e, + ); + } + return innerRes; + }); + } } - } - return res; + return res; + }); } } From dae8956754cad162bf20f49c50a62b65be87e4ac Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Thu, 18 May 2023 21:15:41 +0100 Subject: [PATCH 04/64] feat: Add tracing to validate stage Signed-off-by: Mike Bryant --- .../DefaultCatalogProcessingOrchestrator.ts | 85 +++++++++++-------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts index 8b4f4127f3..aabe703806 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts @@ -269,50 +269,65 @@ export class DefaultCatalogProcessingOrchestrator entity: Entity, context: Context, ): Promise { - // Double check that none of the previous steps tried to change something - // related to the entity ref, which would break downstream - if (stringifyEntityRef(entity) !== context.entityRef) { - throw new ConflictError( - 'Fatal: The entity kind, namespace, or name changed during processing', + return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => { + addEntityAttributes(stageSpan, context.entityRef); + stageSpan.setAttribute( + 'backstage.catalog.processor.stage', + 'validateEntity', ); - } + // Double check that none of the previous steps tried to change something + // related to the entity ref, which would break downstream + if (stringifyEntityRef(entity) !== context.entityRef) { + throw new ConflictError( + 'Fatal: The entity kind, namespace, or name changed during processing', + ); + } - // Validate that the end result is a valid Entity at all - try { - validateEntity(entity); - } catch (e) { - throw new ConflictError( - `Entity envelope for ${context.entityRef} failed validation after preprocessing`, - e, - ); - } + // Validate that the end result is a valid Entity at all + try { + validateEntity(entity); + } catch (e) { + throw new ConflictError( + `Entity envelope for ${context.entityRef} failed validation after preprocessing`, + e, + ); + } - let valid = false; + let valid = false; - for (const processor of this.options.processors) { - if (processor.validateEntityKind) { - try { - const thisValid = await processor.validateEntityKind(entity); - if (thisValid) { - valid = true; - if (this.options.legacySingleProcessorValidation) { - break; + for (const processor of this.options.processors) { + if (processor.validateEntityKind) { + try { + const thisValid = await withActiveSpan( + tracer, + 'ProcessingStep', + async span => { + addEntityAttributes(span, context.entityRef); + addProcessorAttributes(span, 'postProcessEntity', processor); + return await processor.validateEntityKind(entity); + }, + ); + if (thisValid) { + valid = true; + if (this.options.legacySingleProcessorValidation) { + break; + } } + } catch (e) { + throw new InputError( + `Processor ${processor.constructor.name} threw an error while validating the entity ${context.entityRef}`, + e, + ); } - } catch (e) { - throw new InputError( - `Processor ${processor.constructor.name} threw an error while validating the entity ${context.entityRef}`, - e, - ); } } - } - if (!valid) { - throw new InputError( - `No processor recognized the entity ${context.entityRef} as valid, possibly caused by a foreign kind or apiVersion`, - ); - } + if (!valid) { + throw new InputError( + `No processor recognized the entity ${context.entityRef} as valid, possibly caused by a foreign kind or apiVersion`, + ); + } + }); } /** From 9550ae9ad4378b850f3c4c922244eace5b1c3d2c Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Thu, 18 May 2023 21:19:11 +0100 Subject: [PATCH 05/64] feat: Add span for policy stage Signed-off-by: Mike Bryant --- .../DefaultCatalogProcessingOrchestrator.ts | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts index aabe703806..6fa44db1a6 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts @@ -240,26 +240,33 @@ export class DefaultCatalogProcessingOrchestrator * Enforce entity policies making sure that entities conform to a general schema */ private async runPolicyStep(entity: Entity): Promise { - let policyEnforcedEntity: Entity | undefined; - - try { - policyEnforcedEntity = await this.options.policy.enforce(entity); - } catch (e) { - throw new InputError( - `Policy check failed for ${stringifyEntityRef(entity)}`, - e, + return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => { + addEntityAttributes(stageSpan, stringifyEntityRef(entity)); + stageSpan.setAttribute( + 'backstage.catalog.processor.stage', + 'enforcePolicyEntity', ); - } + let policyEnforcedEntity: Entity | undefined; - if (!policyEnforcedEntity) { - throw new Error( - `Policy unexpectedly returned no data for ${stringifyEntityRef( - entity, - )}`, - ); - } + try { + policyEnforcedEntity = await this.options.policy.enforce(entity); + } catch (e) { + throw new InputError( + `Policy check failed for ${stringifyEntityRef(entity)}`, + e, + ); + } - return policyEnforcedEntity; + if (!policyEnforcedEntity) { + throw new Error( + `Policy unexpectedly returned no data for ${stringifyEntityRef( + entity, + )}`, + ); + } + + return policyEnforcedEntity; + }); } /** From bb5ab706d7e6d8413bf74d7243e5de70a5331e47 Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Thu, 18 May 2023 21:24:50 +0100 Subject: [PATCH 06/64] feat: Add spans for read steps Signed-off-by: Mike Bryant --- .../DefaultCatalogProcessingOrchestrator.ts | 122 ++++++++++-------- 1 file changed, 69 insertions(+), 53 deletions(-) diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts index 6fa44db1a6..91a1c2f9a6 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts @@ -344,65 +344,81 @@ export class DefaultCatalogProcessingOrchestrator entity: LocationEntity, context: Context, ): Promise { - const { type = context.location.type, presence = 'required' } = entity.spec; - const targets = new Array(); - if (entity.spec.target) { - targets.push(entity.spec.target); - } - if (entity.spec.targets) { - targets.push(...entity.spec.targets); - } - - for (const maybeRelativeTarget of targets) { - if (type === 'file' && maybeRelativeTarget.endsWith(path.sep)) { - context.collector.generic()( - processingResult.inputError( - context.location, - `LocationEntityProcessor cannot handle ${type} type location with target ${context.location.target} that ends with a path separator`, - ), - ); - continue; - } - const target = toAbsoluteUrl( - this.options.integrations, - context.location, - type, - maybeRelativeTarget, + return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => { + addEntityAttributes(stageSpan, context.entityRef); + stageSpan.setAttribute( + 'backstage.catalog.processor.stage', + 'readLocationEntity', ); + const { type = context.location.type, presence = 'required' } = + entity.spec; + const targets = new Array(); + if (entity.spec.target) { + targets.push(entity.spec.target); + } + if (entity.spec.targets) { + targets.push(...entity.spec.targets); + } - let didRead = false; - for (const processor of this.options.processors) { - if (processor.readLocation) { - try { - const read = await processor.readLocation( - { - type, - target, - presence, - }, - presence === 'optional', - context.collector.forProcessor(processor), - this.options.parser, - context.cache.forProcessor(processor, target), - ); - if (read) { - didRead = true; - break; + for (const maybeRelativeTarget of targets) { + if (type === 'file' && maybeRelativeTarget.endsWith(path.sep)) { + context.collector.generic()( + processingResult.inputError( + context.location, + `LocationEntityProcessor cannot handle ${type} type location with target ${context.location.target} that ends with a path separator`, + ), + ); + continue; + } + const target = toAbsoluteUrl( + this.options.integrations, + context.location, + type, + maybeRelativeTarget, + ); + + let didRead = false; + for (const processor of this.options.processors) { + if (processor.readLocation) { + try { + const read = await withActiveSpan( + tracer, + 'ProcessingStep', + async span => { + addEntityAttributes(span, context.entityRef); + addProcessorAttributes(span, 'readLocationEntity', processor); + return await processor.readLocation( + { + type, + target, + presence, + }, + presence === 'optional', + context.collector.forProcessor(processor), + this.options.parser, + context.cache.forProcessor(processor, target), + ); + }, + ); + if (read) { + didRead = true; + break; + } + } catch (e) { + throw new InputError( + `Processor ${processor.constructor.name} threw an error while reading ${type}:${target}`, + e, + ); } - } catch (e) { - throw new InputError( - `Processor ${processor.constructor.name} threw an error while reading ${type}:${target}`, - e, - ); } } + if (!didRead) { + throw new InputError( + `No processor was able to handle reading of ${type}:${target}`, + ); + } } - if (!didRead) { - throw new InputError( - `No processor was able to handle reading of ${type}:${target}`, - ); - } - } + }); } /** From 5060c6ed7d04b9875a4c50b37a5b33d467e45a69 Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Thu, 18 May 2023 22:52:00 +0100 Subject: [PATCH 07/64] fix: Fix tsc errors Signed-off-by: Mike Bryant --- .../src/processing/DefaultCatalogProcessingOrchestrator.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts index 91a1c2f9a6..dfca5d2058 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Span, SpanStatusCode, trace } from '@opentelemetry/api'; +import { Span, trace } from '@opentelemetry/api'; import { Entity, EntityPolicy, @@ -311,7 +311,7 @@ export class DefaultCatalogProcessingOrchestrator async span => { addEntityAttributes(span, context.entityRef); addProcessorAttributes(span, 'postProcessEntity', processor); - return await processor.validateEntityKind(entity); + return await processor.validateEntityKind!(entity); }, ); if (thisValid) { @@ -387,7 +387,7 @@ export class DefaultCatalogProcessingOrchestrator async span => { addEntityAttributes(span, context.entityRef); addProcessorAttributes(span, 'readLocationEntity', processor); - return await processor.readLocation( + return await processor.readLocation!( { type, target, From ee48005f1871de089ec9eedb85faf0caf3290b5f Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Thu, 18 May 2023 23:15:24 +0100 Subject: [PATCH 08/64] fix: Supply objects that satisfy the type constraints in tests Signed-off-by: Mike Bryant --- .../processing/DefaultCatalogProcessingOrchestrator.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts index ceb6992d7a..fc6855f6df 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts @@ -194,10 +194,12 @@ describe('DefaultCatalogProcessingOrchestrator', () => { it('runs all processor validations when asked to', async () => { const validate = jest.fn(async () => true); - const processor1: Partial = { + const processor1: CatalogProcessor = { + getProcessorName: () => 'processor1', validateEntityKind: validate, }; - const processor2: Partial = { + const processor2: CatalogProcessor = { + getProcessorName: () => 'processor2', validateEntityKind: validate, }; From b5de6d107f026d9ab7bae72d7c6ce665f92570fc Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Fri, 2 Jun 2023 10:15:35 -0400 Subject: [PATCH 09/64] chore: Replace deprecated imports Signed-off-by: Adam Harvey --- docs/features/search/how-to-guides.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/features/search/how-to-guides.md b/docs/features/search/how-to-guides.md index 7de37cb624..382221d781 100644 --- a/docs/features/search/how-to-guides.md +++ b/docs/features/search/how-to-guides.md @@ -51,7 +51,7 @@ The TechDocs plugin has supported integrations to Search, meaning that it provides a default collator factory ready to be used. The purpose of this guide is to walk you through how to register the -[DefaultTechDocsCollatorFactory](https://github.com/backstage/backstage/blob/de294ce5c410c9eb56da6870a1fab795268f60e3/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts) +[DefaultTechDocsCollatorFactory](https://github.com/backstage/backstage/blob/1adc2c7/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts) in your App, so that you can get TechDocs documents indexed. If you have been through the @@ -61,10 +61,10 @@ so, you can go ahead and follow this guide - if not, start by going through the getting started guide. 1. Import the `DefaultTechDocsCollatorFactory` from - `@backstage/plugin-techdocs-backend`. + `@backstage/plugin-search-backend-module-techdocs`. ```typescript - import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; + import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-search-backend-module-techdocs'; ``` 2. If there isn't an existing schedule you'd like to run the collator on, be From f40c8ac5347c0bd145eb1f7c0dc978d9c17f0148 Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Tue, 13 Jun 2023 23:33:50 +0100 Subject: [PATCH 10/64] style: Apply review comments Signed-off-by: Mike Bryant --- .../DefaultCatalogProcessingOrchestrator.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts index dfca5d2058..98f9cd32a5 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts @@ -201,10 +201,7 @@ export class DefaultCatalogProcessingOrchestrator ): Promise { return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => { addEntityAttributes(stageSpan, context.entityRef); - stageSpan.setAttribute( - 'backstage.catalog.processor.stage', - 'preProcessEntity', - ); + stageSpan.setAttribute('backstage.catalog.processor.stage', 'preProcess'); let res = entity; for (const processor of this.options.processors) { @@ -244,7 +241,7 @@ export class DefaultCatalogProcessingOrchestrator addEntityAttributes(stageSpan, stringifyEntityRef(entity)); stageSpan.setAttribute( 'backstage.catalog.processor.stage', - 'enforcePolicyEntity', + 'enforcePolicy', ); let policyEnforcedEntity: Entity | undefined; @@ -278,10 +275,7 @@ export class DefaultCatalogProcessingOrchestrator ): Promise { return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => { addEntityAttributes(stageSpan, context.entityRef); - stageSpan.setAttribute( - 'backstage.catalog.processor.stage', - 'validateEntity', - ); + stageSpan.setAttribute('backstage.catalog.processor.stage', 'validate'); // Double check that none of the previous steps tried to change something // related to the entity ref, which would break downstream if (stringifyEntityRef(entity) !== context.entityRef) { @@ -310,7 +304,7 @@ export class DefaultCatalogProcessingOrchestrator 'ProcessingStep', async span => { addEntityAttributes(span, context.entityRef); - addProcessorAttributes(span, 'postProcessEntity', processor); + addProcessorAttributes(span, 'validateEntityKind', processor); return await processor.validateEntityKind!(entity); }, ); @@ -348,7 +342,7 @@ export class DefaultCatalogProcessingOrchestrator addEntityAttributes(stageSpan, context.entityRef); stageSpan.setAttribute( 'backstage.catalog.processor.stage', - 'readLocationEntity', + 'readLocation', ); const { type = context.location.type, presence = 'required' } = entity.spec; @@ -386,7 +380,7 @@ export class DefaultCatalogProcessingOrchestrator 'ProcessingStep', async span => { addEntityAttributes(span, context.entityRef); - addProcessorAttributes(span, 'readLocationEntity', processor); + addProcessorAttributes(span, 'readLocation', processor); return await processor.readLocation!( { type, From ab51b0c9aee7ee298b99f32aedba573f251e49ee Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Wed, 14 Jun 2023 00:09:02 +0100 Subject: [PATCH 11/64] style: Apply review comments Signed-off-by: Mike Bryant --- .changeset/slimy-kids-jam.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/slimy-kids-jam.md b/.changeset/slimy-kids-jam.md index ea2897bed1..c02daf325e 100644 --- a/.changeset/slimy-kids-jam.md +++ b/.changeset/slimy-kids-jam.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-catalog-backend': minor --- Added OpenTelemetry spans for catalog processing From 73a5205447dccd188558549145dc180562e2c52d Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Wed, 5 Jul 2023 18:47:52 +0100 Subject: [PATCH 12/64] fix: Use real entity instead of ref for attributes Signed-off-by: Mike Bryant --- .../DefaultCatalogProcessingEngine.ts | 2 +- .../DefaultCatalogProcessingOrchestrator.ts | 18 ++++++------ .../catalog-backend/src/util/opentelemetry.ts | 29 ++++++++++++------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts index 2ae104666f..bed1fe6f50 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts @@ -140,7 +140,7 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { processTask: async item => { await withActiveSpan(tracer, 'ProcessingRun', async span => { const track = this.tracker.processStart(item, this.logger); - addEntityAttributes(span, item.entityRef); + addEntityAttributes(span, item.unprocessedEntity); try { const { diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts index 98f9cd32a5..c5eaacbc68 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts @@ -200,7 +200,7 @@ export class DefaultCatalogProcessingOrchestrator context: Context, ): Promise { return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => { - addEntityAttributes(stageSpan, context.entityRef); + addEntityAttributes(stageSpan, entity); stageSpan.setAttribute('backstage.catalog.processor.stage', 'preProcess'); let res = entity; @@ -208,7 +208,7 @@ export class DefaultCatalogProcessingOrchestrator if (processor.preProcessEntity) { let innerRes = res; res = await withActiveSpan(tracer, 'ProcessingStep', async span => { - addEntityAttributes(span, context.entityRef); + addEntityAttributes(span, entity); addProcessorAttributes(span, 'preProcessEntity', processor); try { innerRes = await processor.preProcessEntity!( @@ -238,7 +238,7 @@ export class DefaultCatalogProcessingOrchestrator */ private async runPolicyStep(entity: Entity): Promise { return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => { - addEntityAttributes(stageSpan, stringifyEntityRef(entity)); + addEntityAttributes(stageSpan, entity); stageSpan.setAttribute( 'backstage.catalog.processor.stage', 'enforcePolicy', @@ -274,7 +274,7 @@ export class DefaultCatalogProcessingOrchestrator context: Context, ): Promise { return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => { - addEntityAttributes(stageSpan, context.entityRef); + addEntityAttributes(stageSpan, entity); stageSpan.setAttribute('backstage.catalog.processor.stage', 'validate'); // Double check that none of the previous steps tried to change something // related to the entity ref, which would break downstream @@ -303,7 +303,7 @@ export class DefaultCatalogProcessingOrchestrator tracer, 'ProcessingStep', async span => { - addEntityAttributes(span, context.entityRef); + addEntityAttributes(span, entity); addProcessorAttributes(span, 'validateEntityKind', processor); return await processor.validateEntityKind!(entity); }, @@ -339,7 +339,7 @@ export class DefaultCatalogProcessingOrchestrator context: Context, ): Promise { return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => { - addEntityAttributes(stageSpan, context.entityRef); + addEntityAttributes(stageSpan, entity); stageSpan.setAttribute( 'backstage.catalog.processor.stage', 'readLocation', @@ -379,7 +379,7 @@ export class DefaultCatalogProcessingOrchestrator tracer, 'ProcessingStep', async span => { - addEntityAttributes(span, context.entityRef); + addEntityAttributes(span, entity); addProcessorAttributes(span, 'readLocation', processor); return await processor.readLocation!( { @@ -423,7 +423,7 @@ export class DefaultCatalogProcessingOrchestrator context: Context, ): Promise { return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => { - addEntityAttributes(stageSpan, context.entityRef); + addEntityAttributes(stageSpan, entity); stageSpan.setAttribute( 'backstage.catalog.processor.stage', 'postProcessEntity', @@ -434,7 +434,7 @@ export class DefaultCatalogProcessingOrchestrator if (processor.postProcessEntity) { let innerRes = res; res = await withActiveSpan(tracer, 'ProcessingStep', async span => { - addEntityAttributes(span, context.entityRef); + addEntityAttributes(span, entity); addProcessorAttributes(span, 'postProcessEntity', processor); try { innerRes = await processor.postProcessEntity!( diff --git a/plugins/catalog-backend/src/util/opentelemetry.ts b/plugins/catalog-backend/src/util/opentelemetry.ts index 391869deb8..c2df008452 100644 --- a/plugins/catalog-backend/src/util/opentelemetry.ts +++ b/plugins/catalog-backend/src/util/opentelemetry.ts @@ -15,22 +15,31 @@ */ import { Span, SpanOptions, SpanStatusCode, Tracer } from '@opentelemetry/api'; -import { parseEntityRef } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; export const TRACER_ID = 'backstage-plugin-catalog-backend'; -export function addEntityAttributes(span: Span, entityRef: string) { - try { - const fields = parseEntityRef(entityRef); - span.setAttribute('backstage.entity.kind', fields.kind); - span.setAttribute('backstage.entity.namespace', fields.namespace); - span.setAttribute('backstage.entity.name', fields.name); - } catch (err) { - span.recordException(err); - span.setStatus({ code: SpanStatusCode.ERROR }); +function setAttributeIfDefined(span: Span, attribute: string, value?: string) { + if (value !== null && value !== undefined) { + span.setAttribute(attribute, value); } } +export function addEntityAttributes(span: Span, entity: Entity) { + setAttributeIfDefined(span, 'backstage.entity.apiVersion', entity.apiVersion); + setAttributeIfDefined(span, 'backstage.entity.kind', entity.kind); + setAttributeIfDefined( + span, + 'backstage.entity.metadata.namespace', + entity.metadata?.namespace, + ); + setAttributeIfDefined( + span, + 'backstage.entity.metadata.name', + entity.metadata?.name, + ); +} + // Adapted from https://github.com/open-telemetry/opentelemetry-js/blob/359fbcc40a859057a02b14e84599eac399b8dba7/api/src/trace/SugaredTracer.ts // While waiting for something like https://github.com/open-telemetry/opentelemetry-js/pull/3317 to land upstream From ecf12e5de30dcc522e993f6755de7dc3af4d7cf9 Mon Sep 17 00:00:00 2001 From: alessandro Date: Fri, 7 Jul 2023 06:42:23 +0200 Subject: [PATCH 13/64] feat(catalog-backend-module-gitlab): Added option to skip forked repos in GitlabDiscoveryEntityProvider config Signed-off-by: alessandro --- docs/integrations/gitlab/discovery.md | 1 + plugins/catalog-backend-module-gitlab/src/lib/types.ts | 1 + .../src/providers/GitlabDiscoveryEntityProvider.ts | 7 +++++++ .../catalog-backend-module-gitlab/src/providers/config.ts | 3 +++ 4 files changed, 12 insertions(+) diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md index 4ddb0413b0..235d93ab03 100644 --- a/docs/integrations/gitlab/discovery.md +++ b/docs/integrations/gitlab/discovery.md @@ -23,6 +23,7 @@ catalog: host: gitlab-host # Identifies one of the hosts set up in the integrations branch: main # Optional. Used to discover on a specific branch fallbackBranch: main # Optional. Fallback to be used if there is no default branch configured at the Gitlab repository. It is only used, if `branch` is undefined. Uses `master` as default + skipForkedRepos: false # Optional. If the project is a fork, skip repository group: example-group # Optional. Group and subgroup (if needed) to look for repositories. If not present the whole instance will be scanned entityFilename: catalog-info.yaml # Optional. Defaults to `catalog-info.yaml` projectPattern: '[\s\S]*' # Optional. Filters found projects based on provided patter. Defaults to `[\s\S]*`, which means to not filter anything diff --git a/plugins/catalog-backend-module-gitlab/src/lib/types.ts b/plugins/catalog-backend-module-gitlab/src/lib/types.ts index 341e2a9d9a..9b3a68c107 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/types.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/types.ts @@ -90,4 +90,5 @@ export type GitlabProviderConfig = { groupPattern: RegExp; orgEnabled?: boolean; schedule?: TaskScheduleDefinition; + skipForkedRepos?: boolean; }; diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts index 6001a32a4a..08d2fe48a7 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts @@ -177,6 +177,13 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { continue; } + if ( + this.config.skipForkedRepos && + project.hasOwnProperty('forked_from_project') + ) { + continue; + } + if ( !this.config.branch && this.config.fallbackBranch === '*' && diff --git a/plugins/catalog-backend-module-gitlab/src/providers/config.ts b/plugins/catalog-backend-module-gitlab/src/providers/config.ts index cd15c4825c..c34cb81808 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/config.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/config.ts @@ -43,6 +43,8 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig { config.getOptionalString('groupPattern') ?? /[\s\S]*/, ); const orgEnabled: boolean = config.getOptionalBoolean('orgEnabled') ?? false; + const skipForkedRepos: boolean = + config.getOptionalBoolean('skipForkedRepos') ?? false; const schedule = config.has('schedule') ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) @@ -60,6 +62,7 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig { groupPattern, schedule, orgEnabled, + skipForkedRepos, }; } From e6c721439f37aeb723d121aed96dbde4f86284d9 Mon Sep 17 00:00:00 2001 From: alessandro Date: Fri, 7 Jul 2023 06:44:11 +0200 Subject: [PATCH 14/64] feat(catalog-backend-module-gitlab): Added changeset Signed-off-by: alessandro --- .changeset/khaki-flies-draw.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/khaki-flies-draw.md diff --git a/.changeset/khaki-flies-draw.md b/.changeset/khaki-flies-draw.md new file mode 100644 index 0000000000..ada45dea06 --- /dev/null +++ b/.changeset/khaki-flies-draw.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': patch +--- + +Added option to skip forked repos in GitlabDiscoveryEntityProvider From d913b37a8b32c7fcf72a79982c4e3157cda9807d Mon Sep 17 00:00:00 2001 From: alessandro Date: Fri, 7 Jul 2023 06:50:55 +0200 Subject: [PATCH 15/64] feat(catalog-backend-module-gitlab): Added tests Signed-off-by: alessandro --- .../GitlabDiscoveryEntityProvider.test.ts | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts index 75a4c6414c..7508b35b84 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts @@ -343,6 +343,119 @@ describe('GitlabDiscoveryEntityProvider', () => { }); }); + it('should filter fork projects', async () => { + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'test-gitlab', + apiBaseUrl: 'https://api.gitlab.example/api/v4', + token: '1234', + }, + ], + }, + catalog: { + providers: { + gitlab: { + 'test-id': { + host: 'test-gitlab', + skipForkedRepos: true, + }, + }, + }, + }, + }); + const schedule = new PersistingTaskRunner(); + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + const provider = GitlabDiscoveryEntityProvider.fromConfig(config, { + logger, + schedule, + })[0]; + + server.use( + rest.get( + `https://api.gitlab.example/api/v4/projects`, + (_req, res, ctx) => { + const response = [ + { + id: 123, + default_branch: 'master', + archived: false, + last_activity_at: new Date().toString(), + web_url: 'https://api.gitlab.example/test-group/test-repo', + path_with_namespace: 'test-group/test-repo', + forked_from_project: { + id: 13083, + }, + }, + { + id: 124, + default_branch: 'master', + archived: false, + last_activity_at: new Date().toString(), + web_url: 'https://api.gitlab.example/john/example', + path_with_namespace: 'john/example', + }, + ]; + return res(ctx.json(response)); + }, + ), + rest.head( + 'https://api.gitlab.example/api/v4/projects/test-group%2Ftest-repo/repository/files/catalog-info.yaml', + (req, res, ctx) => { + if (req.url.searchParams.get('ref') === 'master') { + return res(ctx.status(200)); + } + return res(ctx.status(404, 'Not Found')); + }, + ), + rest.head( + 'https://api.gitlab.example/api/v4/projects/john%2Fexample/repository/files/catalog-info.yaml', + (req, res, ctx) => { + if (req.url.searchParams.get('ref') === 'master') { + return res(ctx.status(200)); + } + return res(ctx.status(404, 'Not Found')); + }, + ), + ); + + await provider.connect(entityProviderConnection); + + await provider.refresh(logger); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities: [ + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'url:https://api.gitlab.example/john/example/-/blob/master/catalog-info.yaml', + 'backstage.io/managed-by-origin-location': + 'url:https://api.gitlab.example/john/example/-/blob/master/catalog-info.yaml', + }, + name: 'generated-2045212e5b3e9e6bacf51cec709e362282e3cda9', + }, + spec: { + presence: 'optional', + target: + 'https://api.gitlab.example/john/example/-/blob/master/catalog-info.yaml', + type: 'url', + }, + }, + locationKey: 'GitlabDiscoveryEntityProvider:test-id', + }, + ], + }); + }); + it('fail without schedule and scheduler', () => { const config = new ConfigReader({ integrations: { From 0de20357eee659a8bfa7fef6fe50d8ba851da90f Mon Sep 17 00:00:00 2001 From: alessandro Date: Fri, 7 Jul 2023 07:05:33 +0200 Subject: [PATCH 16/64] feat(catalog-backend-module-gitlab): Fix tests Signed-off-by: alessandro --- .../src/providers/config.test.ts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts index 1035538939..bac56cc6f3 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts @@ -59,6 +59,7 @@ describe('config', () => { userPattern: /[\s\S]*/, orgEnabled: false, schedule: undefined, + skipForkedRepos: false, }), ); }); @@ -95,6 +96,45 @@ describe('config', () => { userPattern: /[\s\S]*/, orgEnabled: false, schedule: undefined, + skipForkedRepos: false, + }), + ); + }); + + it('valid config with skipForkedRepos', () => { + const config = new ConfigReader({ + catalog: { + providers: { + gitlab: { + test: { + group: 'group', + host: 'host', + branch: 'not-master', + fallbackBranch: 'main', + entityFilename: 'custom-file.yaml', + skipForkedRepos: true, + }, + }, + }, + }, + }); + + const result = readGitlabConfigs(config); + expect(result).toHaveLength(1); + result.forEach(r => + expect(r).toStrictEqual({ + id: 'test', + group: 'group', + branch: 'not-master', + fallbackBranch: 'main', + host: 'host', + catalogFile: 'custom-file.yaml', + projectPattern: /[\s\S]*/, + groupPattern: /[\s\S]*/, + userPattern: /[\s\S]*/, + orgEnabled: false, + schedule: undefined, + skipForkedRepos: true, }), ); }); @@ -133,6 +173,7 @@ describe('config', () => { groupPattern: /[\s\S]*/, userPattern: /[\s\S]*/, orgEnabled: false, + skipForkedRepos: false, schedule: { frequency: Duration.fromISO('PT30M'), timeout: { From 2b4f77a4e900879de66d19f46a1f538ad78d8842 Mon Sep 17 00:00:00 2001 From: Brian Phillips <28457+brianphillips@users.noreply.github.com> Date: Fri, 21 Jul 2023 10:20:49 -0500 Subject: [PATCH 17/64] Allow package prefixes to be customized for DevTools dependency listing Signed-off-by: Brian Phillips <28457+brianphillips@users.noreply.github.com> --- .changeset/kind-cougars-allow.md | 5 +++++ plugins/devtools-backend/config.d.ts | 5 +++++ .../devtools-backend/src/api/DevToolsBackendApi.ts | 7 ++++++- plugins/devtools/README.md | 14 +++++++++++++- 4 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 .changeset/kind-cougars-allow.md diff --git a/.changeset/kind-cougars-allow.md b/.changeset/kind-cougars-allow.md new file mode 100644 index 0000000000..058bec5f2e --- /dev/null +++ b/.changeset/kind-cougars-allow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-devtools-backend': patch +--- + +Add DevTools configuration to enable dependency listing to be filtered with custom prefixes diff --git a/plugins/devtools-backend/config.d.ts b/plugins/devtools-backend/config.d.ts index 15e70afcf8..79d1d6159f 100644 --- a/plugins/devtools-backend/config.d.ts +++ b/plugins/devtools-backend/config.d.ts @@ -40,5 +40,10 @@ export interface Config { target: string; }>; }; + /** + * A list of package prefixes that DevTools will use for filtering all available dependencies + * (default is ["@backstage"]) + */ + packagePrefixes?: string[]; }; } diff --git a/plugins/devtools-backend/src/api/DevToolsBackendApi.ts b/plugins/devtools-backend/src/api/DevToolsBackendApi.ts index efd0407b31..eb5dc9f860 100644 --- a/plugins/devtools-backend/src/api/DevToolsBackendApi.ts +++ b/plugins/devtools-backend/src/api/DevToolsBackendApi.ts @@ -219,7 +219,12 @@ export class DevToolsBackendApi { const lockfilePath = paths.resolveTargetRoot('yarn.lock'); const lockfile = await Lockfile.load(lockfilePath); - const deps = [...lockfile.keys()].filter(n => n.startsWith('@backstage/')); + const prefixes = this.config.getOptionalStringArray( + 'devTools.packagePrefixes', + ) ?? ['@backstage/']; + const deps = [...lockfile.keys()].filter(n => + prefixes.some(prefix => n.startsWith(prefix)), + ); const infoDependencies: PackageDependency[] = []; for (const dep of deps) { diff --git a/plugins/devtools/README.md b/plugins/devtools/README.md index 09e76caaa4..e59f4df132 100644 --- a/plugins/devtools/README.md +++ b/plugins/devtools/README.md @@ -410,9 +410,21 @@ export const customDevToolsPage = ; The following sections outline the configuration for the DevTools plugin +### Package Dependencies + +By default, only packages with names starting with `@backstage/` will be listed on the main "Info" tab. If you would like additional packages to be listed, you can specify the package prefixes in your `app-config.yaml`. For example, to include backstage plugins provided by the core application as well as `@roadiehq` and `@spotify`: + +```yaml +devTools: + packagePrefixes: + - @backstage/ + - @roadiehq/backstage- + - @spotify/backstage- +``` + ### External Dependencies Configuration -If you decide to use the External Dependencies tab then you'll need to setup the configuration for it in your `app-config.yaml`, if there is no config setup then the tab will be empty. Here's an example: +If you decide to use the External Dependencies tab then you'll need to setup the configuration for it in your `app-config.yaml`. If there is no endpoints configured, then the tab will be empty. Here's an example: ```yaml devTools: From b9379b45bb1b296377cb624b22eda32713c3a2df Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Fri, 21 Jul 2023 19:11:40 +0100 Subject: [PATCH 18/64] chore: Use PromiseLike instead of Promise Follow review comments, use PromiseLike with a type guard Signed-off-by: Mike Bryant --- .../catalog-backend/src/util/opentelemetry.ts | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/plugins/catalog-backend/src/util/opentelemetry.ts b/plugins/catalog-backend/src/util/opentelemetry.ts index c2df008452..3fb83d7909 100644 --- a/plugins/catalog-backend/src/util/opentelemetry.ts +++ b/plugins/catalog-backend/src/util/opentelemetry.ts @@ -50,23 +50,38 @@ const onException = (e: Error, span: Span) => { }); }; +function isPromiseLike(obj: PromiseLike | S): obj is PromiseLike { + return ( + !!obj && + (typeof obj === 'object' || typeof obj === 'function') && + 'then' in obj && + typeof obj.then === 'function' + ); +} + function handleFn ReturnType>( span: Span, fn: F, ): ReturnType { try { - const ret = fn(span) as Promise>; + const ret = fn(span); + // if fn is an async function attach a recordException and spanEnd callback to the promise - if (typeof ret.then === 'function' && typeof ret.catch === 'function') { - return ret - .catch((e: Error) => { + if (isPromiseLike(ret)) { + ret.then( + () => { + span.end(); + }, + e => { onException(e, span); - throw e; - }) - .finally(() => span.end()) as ReturnType; + span.end(); + }, + ); + } else { + span.end(); } - span.end(); - return ret as ReturnType; + + return ret; } catch (e) { onException(e, span); span.end(); From b3da8fb86302ad688399b05530723a8dc9b5c310 Mon Sep 17 00:00:00 2001 From: cloudoutloud <39462069+cloudoutloud@users.noreply.github.com> Date: Sun, 23 Jul 2023 18:55:22 +0100 Subject: [PATCH 19/64] Update ts examples in aws alb auth documentation Signed-off-by: cloudoutloud <39462069+cloudoutloud@users.noreply.github.com> --- .../docs/tutorials/aws-alb-aad-oidc-auth.md | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md index 1bdf9cc367..dca6f096c3 100644 --- a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md +++ b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md @@ -58,6 +58,7 @@ When using ALB authentication Backstage will only be loaded once the user has su import React from 'react'; import { UserIdentity } from '@backstage/core-components'; import { SignInPageProps } from '@backstage/core-app-api'; +import { useApi, configApiRef } from '@backstage/core-plugin-api'; const SampleSignInComponent: any = (props: SignInPageProps) => { const [error, setError] = React.useState(); @@ -135,12 +136,14 @@ export default async function createPlugin({ database, config, discovery, + tokenManager, }: PluginEnvironment): Promise { return await createRouter({ logger, config, database, discovery, + tokenManager, providerFactories: { awsalb: providers.awsAlb.create({ authHandler: async ({ fullProfile }) => { @@ -168,25 +171,29 @@ export default async function createPlugin({ }; }, signIn: { - resolver: async ({ profile: { email } }, ctx) => { - const [id] = email?.split('@') ?? ''; - // Fetch from an external system that returns entity claims like: - // ['user:default/breanna.davison', ...] - const userEntityRef = stringifyEntityRef({ + resolver: async ({ profile }, ctx) => { + if (!profile.email) { + throw new Error('Profile contained no email'); + } + + const [id] = profile.email.split('@'); + if (!id) { + throw new Error('Invalid email format'); + } + + const userRef = stringifyEntityRef({ kind: 'User', - namespace: DEFAULT_NAMESPACE, name: id, + namespace: DEFAULT_NAMESPACE, }); - // Resolve group membership from the Backstage catalog - const fullEnt = - await ctx.catalogIdentityClient.resolveCatalogMembership({ - entityRefs: [id].concat([userEntityRef]), - logger: ctx.logger, - }); - const token = await ctx.tokenIssuer.issueToken({ - claims: { sub: userEntityRef, ent: fullEnt }, + const { token } = await ctx.issueToken({ + claims: { + sub: userRef, + ent: [userRef], + }, }); + return { id, token }; }, }, From 032f748151bfa5a5ea7873922dec25b088d1f1eb Mon Sep 17 00:00:00 2001 From: cloudoutloud <39462069+cloudoutloud@users.noreply.github.com> Date: Mon, 24 Jul 2023 19:36:38 +0100 Subject: [PATCH 20/64] Update ts examples in aws alb auth documentation error Signed-off-by: cloudoutloud <39462069+cloudoutloud@users.noreply.github.com> --- contrib/docs/tutorials/aws-alb-aad-oidc-auth.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md index dca6f096c3..f28a62c018 100644 --- a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md +++ b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md @@ -91,8 +91,8 @@ const SampleSignInComponent: any = (props: SignInPageProps) => { }, }), ); - } catch (err) { - setError(err.message); + } catch (err: any) { + setError(err.message as string); } } }, [config]); From 366a6b98fc567d11a254f9f03fea4c9d2fa84643 Mon Sep 17 00:00:00 2001 From: Brian Phillips <28457+brianphillips@users.noreply.github.com> Date: Mon, 24 Jul 2023 21:44:26 -0500 Subject: [PATCH 21/64] code review feedback Signed-off-by: Brian Phillips <28457+brianphillips@users.noreply.github.com> --- .changeset/kind-cougars-allow.md | 11 ++++++++++- plugins/devtools-backend/config.d.ts | 11 ++++++++--- .../devtools-backend/src/api/DevToolsBackendApi.ts | 6 +++--- plugins/devtools/README.md | 12 ++++++------ 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/.changeset/kind-cougars-allow.md b/.changeset/kind-cougars-allow.md index 058bec5f2e..d37c4c5cf2 100644 --- a/.changeset/kind-cougars-allow.md +++ b/.changeset/kind-cougars-allow.md @@ -2,4 +2,13 @@ '@backstage/plugin-devtools-backend': patch --- -Add DevTools configuration to enable dependency listing to be filtered with custom prefixes +Add DevTools configuration to enable dependency listing to be filtered with custom prefixes. For instance, in your `app-config.yaml`: + +```yaml +devTools: + info: + packagePrefixes: + - @backstage/ + - @roadiehq/backstage- + - @spotify/backstage- +``` diff --git a/plugins/devtools-backend/config.d.ts b/plugins/devtools-backend/config.d.ts index 79d1d6159f..9fc7cf1762 100644 --- a/plugins/devtools-backend/config.d.ts +++ b/plugins/devtools-backend/config.d.ts @@ -41,9 +41,14 @@ export interface Config { }>; }; /** - * A list of package prefixes that DevTools will use for filtering all available dependencies - * (default is ["@backstage"]) + * Info configuration */ - packagePrefixes?: string[]; + info?: { + /** + * A list of package prefixes that DevTools will use for filtering all available dependencies + * (default is ["@backstage"]) + */ + packagePrefixes?: string[]; + }; }; } diff --git a/plugins/devtools-backend/src/api/DevToolsBackendApi.ts b/plugins/devtools-backend/src/api/DevToolsBackendApi.ts index eb5dc9f860..c1b1a1fa9a 100644 --- a/plugins/devtools-backend/src/api/DevToolsBackendApi.ts +++ b/plugins/devtools-backend/src/api/DevToolsBackendApi.ts @@ -219,9 +219,9 @@ export class DevToolsBackendApi { const lockfilePath = paths.resolveTargetRoot('yarn.lock'); const lockfile = await Lockfile.load(lockfilePath); - const prefixes = this.config.getOptionalStringArray( - 'devTools.packagePrefixes', - ) ?? ['@backstage/']; + const prefixes = ['@backstage', '@internal'].concat( + this.config.getOptionalStringArray('devTools.info.packagePrefixes') ?? [], + ); const deps = [...lockfile.keys()].filter(n => prefixes.some(prefix => n.startsWith(prefix)), ); diff --git a/plugins/devtools/README.md b/plugins/devtools/README.md index e59f4df132..c3c3fd9240 100644 --- a/plugins/devtools/README.md +++ b/plugins/devtools/README.md @@ -408,18 +408,18 @@ export const customDevToolsPage = ; ## Configuration -The following sections outline the configuration for the DevTools plugin +The following sections outline the configuration for the DevTools plugin. ### Package Dependencies -By default, only packages with names starting with `@backstage/` will be listed on the main "Info" tab. If you would like additional packages to be listed, you can specify the package prefixes in your `app-config.yaml`. For example, to include backstage plugins provided by the core application as well as `@roadiehq` and `@spotify`: +By default, only packages with names starting with `@backstage` and `@internal` will be listed on the main "Info" tab. If you would like additional packages to be listed, you can specify the package prefixes (not regular expressions) in your `app-config.yaml`. For example, to not only provide version information about backstage plugins provided by the core application (`@backstage/*` modules) but also `@roadiehq` and `@spotify` plugins, you can specify this configuration: ```yaml devTools: - packagePrefixes: - - @backstage/ - - @roadiehq/backstage- - - @spotify/backstage- + info: + packagePrefixes: + - @roadiehq/backstage- + - @spotify/backstage- ``` ### External Dependencies Configuration From 0d347efc8f18f3acb566e4d4a83478d0151e8ebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 25 Jul 2023 09:55:38 +0200 Subject: [PATCH 22/64] Use fetchContents direectly instead of a fetchPlainAction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/odd-avocados-buy.md | 5 +++++ .../confluence/confluenceToMarkdown.test.ts | 4 ++-- .../actions/confluence/confluenceToMarkdown.ts | 17 ++++++++--------- 3 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 .changeset/odd-avocados-buy.md diff --git a/.changeset/odd-avocados-buy.md b/.changeset/odd-avocados-buy.md new file mode 100644 index 0000000000..9e71c19fc0 --- /dev/null +++ b/.changeset/odd-avocados-buy.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch +--- + +Use `fetchContents` directly instead of a `fetchPlainAction` diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts index b3e2925265..4e4f3091f1 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts @@ -157,7 +157,7 @@ describe('confluence:transform:markdown', () => { expect(logger.info).toHaveBeenCalledWith( `Fetching the mkdocs.yml catalog from https://notreal.github.com/space/backstage/mkdocs.yml`, ); - expect(logger.info).toHaveBeenCalledTimes(6); + expect(logger.info).toHaveBeenCalledTimes(5); expect(createWriteStream).toHaveBeenCalledTimes(1); expect(readFile).toHaveBeenCalledTimes(1); expect(writeFile).toHaveBeenCalledTimes(1); @@ -204,7 +204,7 @@ describe('confluence:transform:markdown', () => { expect(logger.info).toHaveBeenCalledWith( `Fetching the mkdocs.yml catalog from https://notreal.github.com/space/backstage/mkdocs.yml`, ); - expect(logger.info).toHaveBeenCalledTimes(6); + expect(logger.info).toHaveBeenCalledTimes(5); expect(createWriteStream).not.toHaveBeenCalled(); expect(readFile).toHaveBeenCalledTimes(1); diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts index 9b666fd99e..428f392555 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts @@ -16,7 +16,7 @@ import { Config } from '@backstage/config'; import { UrlReader } from '@backstage/backend-common'; import { ScmIntegrations } from '@backstage/integration'; -import { createFetchPlainAction } from '@backstage/plugin-scaffolder-backend'; +import { fetchContents } from '@backstage/plugin-scaffolder-backend'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { InputError, ConflictError } from '@backstage/errors'; import { NodeHtmlMarkdown } from 'node-html-markdown'; @@ -41,7 +41,6 @@ export const createConfluenceToMarkdownAction = (options: { config: Config; }) => { const { config, reader, integrations } = options; - const fetchPlainAction = createFetchPlainAction({ reader, integrations }); type Obj = { [key: string]: string; }; @@ -82,18 +81,18 @@ export const createConfluenceToMarkdownAction = (options: { parsedRepoUrl.filepath.lastIndexOf('/') + 1, ); const dirPath = ctx.workspacePath; + const repoFileDir = `${dirPath}/${parsedRepoUrl.filepath}`; let productArray: string[][] = []; ctx.logger.info(`Fetching the mkdocs.yml catalog from ${repoUrl}`); // This grabs the files from Github - const repoFileDir = `${dirPath}/${parsedRepoUrl.filepath}`; - await fetchPlainAction.handler({ - ...ctx, - input: { - url: `https://${parsedRepoUrl.resource}/${parsedRepoUrl.owner}/${parsedRepoUrl.name}`, - targetPath: dirPath, - }, + await fetchContents({ + reader, + integrations, + baseUrl: ctx.templateInfo?.baseUrl, + fetchUrl: `https://${parsedRepoUrl.resource}/${parsedRepoUrl.owner}/${parsedRepoUrl.name}`, + outputPath: ctx.workspacePath, }); for (const url of confluenceUrls) { From 5c0da7f277a3404bd03b0f79c952653479713004 Mon Sep 17 00:00:00 2001 From: Gio <6011354+gioufop@users.noreply.github.com> Date: Tue, 25 Jul 2023 08:44:23 -0300 Subject: [PATCH 23/64] git commit -s -m 'Add Zenklub to ADOPTERS.md' Signed-off-by: Gio <6011354+gioufop@users.noreply.github.com> --- ADOPTERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index 926c303313..cd340fd5f9 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -254,3 +254,4 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Celonis](https://celonis.com) | [@georgeyord](https://github.com/georgeyord), [@LauraMoraB](https://github.com/LauraMoraB), [@mariosant](https://github.com/mariosant), [@Rbillon59](https://github.com/Rbillon59) | Internal developer and product portal implementation. | | [Rabobank](https://www.rabobank.com) | [Willem Dekker](https://github.com/wdekker), [Sridhar Gnanasekaran](https://github.com/srid99), [Ruben Ernst](https://github.com/Ruben-E) | At Rabobank, our mission is to make life better for engineers. After exploring various options in the market, we've discovered that Backstage provides the perfect foundation for creating a platform that meets our engineers' needs and helps us achieve our goals. We want to make our engineers happy by offering standardized services, freeing them up to focus on delivering value. Our vision is to create a one-stop platform where engineers can find everything related to software and services. In practice, we're excited about using all the cool features in Backstage, and we're even planning to build our own plugins. | | [JB Hi-Fi](https://www.jbhifi.com.au) | [@bahman](https://github.com/bahman-jb/) | We use Backstage as a central place for all of our components/apis across multiple teams. It helps us to quickly identify the ownership of a component/api, find the related links and have a quick access to CI/CD pipelines. | +| [Zenklub](https://www.zenklub.com.br) | [@zenklub](https://github.com/zenklub), [@gioufop](https://github.com/gioufop) | Developer portal, services catalog and centralization of metrics from Grafana Stack and AWS. Furthermore, centralization of documentation and infra details like Tools, Network services and so on. | \ No newline at end of file From 941aa42ef65fc0e435167db837439121e4cbeb7e Mon Sep 17 00:00:00 2001 From: alessandro Date: Thu, 27 Jul 2023 12:40:49 +0200 Subject: [PATCH 24/64] feat(catalog-backend-module-gitlab): Add skipForkedRepos field in config.d.ts Signed-off-by: alessandro --- plugins/catalog-backend-module-gitlab/config.d.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/catalog-backend-module-gitlab/config.d.ts b/plugins/catalog-backend-module-gitlab/config.d.ts index 00ad0ef032..f5afc71093 100644 --- a/plugins/catalog-backend-module-gitlab/config.d.ts +++ b/plugins/catalog-backend-module-gitlab/config.d.ts @@ -60,6 +60,10 @@ export interface Config { * (Optional) RegExp for the Group Name Pattern */ groupPattern?: RegExp; + /** + * (Optional) Skip forked repository + */ + skipForkedRepos?: boolean; } >; }; From 0bbe37dd6cf3af57b8ac3caa41695560fb2efe66 Mon Sep 17 00:00:00 2001 From: Abhijeet Gaurav Date: Thu, 27 Jul 2023 16:21:13 +0000 Subject: [PATCH 25/64] fixed:Broken links in documentation Signed-off-by: Abhijeet Gaurav --- docs/backend-system/architecture/01-index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/backend-system/architecture/01-index.md b/docs/backend-system/architecture/01-index.md index aff730b0d1..256d43bc4c 100644 --- a/docs/backend-system/architecture/01-index.md +++ b/docs/backend-system/architecture/01-index.md @@ -59,7 +59,7 @@ Just like plugins, modules also have access to services and can depend on their A detailed explanation of the package architecture can be found in the [Backstage Architecture -Overview](../../overview/architecture-overview.md#package-architecture). The +Overview](../../overview/architecture-overview/#package-architecture). The most important packages to consider for this system are the following: - `plugin--backend` houses the implementation of the backend plugins From 290eff6692aa3a72beb306566fe2052fbc6a73eb Mon Sep 17 00:00:00 2001 From: Matthew Clarke Date: Thu, 27 Jul 2023 15:09:23 -0400 Subject: [PATCH 26/64] feat: GKE Resource provider (#18759) * feat: GKE Resource provider Signed-off-by: Matthew Clarke * feat: configurable parents Signed-off-by: Matthew Clarke * chore: changeset Signed-off-by: Matthew Clarke * test: happy path test Signed-off-by: Matthew Clarke * chore: typos Signed-off-by: Matthew Clarke * docs: api-reports Signed-off-by: Matthew Clarke * docs: readme Signed-off-by: Matthew Clarke --------- Signed-off-by: Matthew Clarke --- .changeset/tough-dolphins-care.md | 5 + .../catalog-backend-module-gcp/.eslintrc.js | 1 + plugins/catalog-backend-module-gcp/README.md | 41 +++ .../alpha-api-report.md | 7 + .../catalog-backend-module-gcp/api-report.md | 48 ++++ .../catalog-backend-module-gcp/config.d.ts | 45 ++++ .../catalog-backend-module-gcp/package.json | 66 +++++ .../catalog-backend-module-gcp/src/alpha.ts | 23 ++ .../catalog-backend-module-gcp/src/index.ts | 24 ++ .../catalogModuleGcpGkeEntityProvider.ts | 52 ++++ .../src/module/index.ts | 17 ++ .../src/providers/GkeEntityProvider.test.ts | 251 ++++++++++++++++++ .../src/providers/GkeEntityProvider.ts | 223 ++++++++++++++++ .../src/providers/index.ts | 17 ++ .../src/setupTests.ts | 17 ++ yarn.lock | 25 +- 16 files changed, 858 insertions(+), 4 deletions(-) create mode 100644 .changeset/tough-dolphins-care.md create mode 100644 plugins/catalog-backend-module-gcp/.eslintrc.js create mode 100644 plugins/catalog-backend-module-gcp/README.md create mode 100644 plugins/catalog-backend-module-gcp/alpha-api-report.md create mode 100644 plugins/catalog-backend-module-gcp/api-report.md create mode 100644 plugins/catalog-backend-module-gcp/config.d.ts create mode 100644 plugins/catalog-backend-module-gcp/package.json create mode 100644 plugins/catalog-backend-module-gcp/src/alpha.ts create mode 100644 plugins/catalog-backend-module-gcp/src/index.ts create mode 100644 plugins/catalog-backend-module-gcp/src/module/catalogModuleGcpGkeEntityProvider.ts create mode 100644 plugins/catalog-backend-module-gcp/src/module/index.ts create mode 100644 plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.test.ts create mode 100644 plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.ts create mode 100644 plugins/catalog-backend-module-gcp/src/providers/index.ts create mode 100644 plugins/catalog-backend-module-gcp/src/setupTests.ts diff --git a/.changeset/tough-dolphins-care.md b/.changeset/tough-dolphins-care.md new file mode 100644 index 0000000000..7d9739ec10 --- /dev/null +++ b/.changeset/tough-dolphins-care.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gcp': minor +--- + +Added GCP catalog plugin with GKE provider diff --git a/plugins/catalog-backend-module-gcp/.eslintrc.js b/plugins/catalog-backend-module-gcp/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/catalog-backend-module-gcp/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/catalog-backend-module-gcp/README.md b/plugins/catalog-backend-module-gcp/README.md new file mode 100644 index 0000000000..d1ef926310 --- /dev/null +++ b/plugins/catalog-backend-module-gcp/README.md @@ -0,0 +1,41 @@ +# Catalog Backend Module for GCP + +This is an extension module to the plugin-catalog-backend plugin, containing catalog processors and providers to ingest GCP resources as `Resource` kind entities. + +## installation + +Register the plugin in `catalog.ts`` + +```typescript +import { GkeEntityProvider } from '@backstage/plugin-catalog-backend-module-gcp'; + +... + +builder.addEntityProvider( + GkeEntityProvider.fromConfig({ + logger: env.logger, + scheduler: env.scheduler, + config: env.config + }) +); +``` + +Update `app-config.yaml` as follows: + +```yaml +catalog: + providers: + gcp: + gke: + parents: + # consult https://cloud.google.com/kubernetes-engine/docs/ for valid values + # list all clusters in the project + - 'projects/some-project/locations/-' + # list all clusters in the region, in the project + - 'projects/some-other-project/locations/some-region' + schedule: # optional; same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } +``` diff --git a/plugins/catalog-backend-module-gcp/alpha-api-report.md b/plugins/catalog-backend-module-gcp/alpha-api-report.md new file mode 100644 index 0000000000..6e6f0427e9 --- /dev/null +++ b/plugins/catalog-backend-module-gcp/alpha-api-report.md @@ -0,0 +1,7 @@ +## API Report File for "@backstage/plugin-catalog-backend-module-gcp" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +``` diff --git a/plugins/catalog-backend-module-gcp/api-report.md b/plugins/catalog-backend-module-gcp/api-report.md new file mode 100644 index 0000000000..6f963ed51d --- /dev/null +++ b/plugins/catalog-backend-module-gcp/api-report.md @@ -0,0 +1,48 @@ +## API Report File for "@backstage/plugin-catalog-backend-module-gcp" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; +import * as container from '@google-cloud/container'; +import { EntityProvider } from '@backstage/plugin-catalog-node'; +import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; +import { Logger } from 'winston'; +import { SchedulerService } from '@backstage/backend-plugin-api'; + +// @public +export const catalogModuleGcpGkeEntityProvider: () => BackendFeature; + +// @public +export class GkeEntityProvider implements EntityProvider { + // (undocumented) + connect(connection: EntityProviderConnection): Promise; + // (undocumented) + static fromConfig({ + logger, + scheduler, + config, + }: { + logger: Logger; + scheduler: SchedulerService; + config: Config; + }): GkeEntityProvider; + // (undocumented) + static fromConfigWithClient({ + logger, + scheduler, + config, + clusterManagerClient, + }: { + logger: Logger; + scheduler: SchedulerService; + config: Config; + clusterManagerClient: container.v1.ClusterManagerClient; + }): GkeEntityProvider; + // (undocumented) + getProviderName(): string; + // (undocumented) + refresh(): Promise; +} +``` diff --git a/plugins/catalog-backend-module-gcp/config.d.ts b/plugins/catalog-backend-module-gcp/config.d.ts new file mode 100644 index 0000000000..d6884d32ce --- /dev/null +++ b/plugins/catalog-backend-module-gcp/config.d.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; + +export interface Config { + catalog?: { + /** + * List of provider-specific options and attributes + */ + providers?: { + /** + * GCPCatalogModuleConfig configuration + */ + gcp?: { + /** + * Config for GKE clusters + */ + gke?: { + /** + * Locations to list clusters from + */ + parents: string[]; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule: TaskScheduleDefinitionConfig; + }; + }; + }; + }; +} diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json new file mode 100644 index 0000000000..12285d26eb --- /dev/null +++ b/plugins/catalog-backend-module-gcp/package.json @@ -0,0 +1,66 @@ +{ + "name": "@backstage/plugin-catalog-backend-module-gcp", + "description": "A Backstage catalog backend module that helps integrate towards GCP", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "exports": { + ".": "./src/index.ts", + "./alpha": "./src/alpha.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "alpha": [ + "src/alpha.ts" + ], + "package.json": [ + "package.json" + ] + } + }, + "backstage": { + "role": "backend-plugin-module" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/catalog-backend-module-gcp" + }, + "keywords": [ + "backstage" + ], + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" + }, + "dependencies": { + "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/backend-tasks": "workspace:^", + "@backstage/config": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", + "@backstage/plugin-kubernetes-common": "workspace:^", + "@google-cloud/container": "^4.15.0", + "winston": "^3.2.1" + }, + "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^" + }, + "files": [ + "config.d.ts", + "dist" + ], + "configSchema": "config.d.ts" +} diff --git a/plugins/catalog-backend-module-gcp/src/alpha.ts b/plugins/catalog-backend-module-gcp/src/alpha.ts new file mode 100644 index 0000000000..9eec69e76f --- /dev/null +++ b/plugins/catalog-backend-module-gcp/src/alpha.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * A Backstage catalog backend module that helps integrate towards GCP + * + * @packageDocumentation + */ + +export {}; diff --git a/plugins/catalog-backend-module-gcp/src/index.ts b/plugins/catalog-backend-module-gcp/src/index.ts new file mode 100644 index 0000000000..fb3984a04d --- /dev/null +++ b/plugins/catalog-backend-module-gcp/src/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * A Backstage catalog backend module that helps integrate towards GCP + * + * @packageDocumentation + */ + +export * from './providers'; +export * from './module'; diff --git a/plugins/catalog-backend-module-gcp/src/module/catalogModuleGcpGkeEntityProvider.ts b/plugins/catalog-backend-module-gcp/src/module/catalogModuleGcpGkeEntityProvider.ts new file mode 100644 index 0000000000..c13c6cbe0e --- /dev/null +++ b/plugins/catalog-backend-module-gcp/src/module/catalogModuleGcpGkeEntityProvider.ts @@ -0,0 +1,52 @@ +/* + * Copyright 2022 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 { loggerToWinstonLogger } from '@backstage/backend-common'; +import { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; +import { GkeEntityProvider } from '../providers/GkeEntityProvider'; + +/** + * Registers the GcpGkeEntityProvider with the catalog processing extension point. + * + * @public + */ +export const catalogModuleGcpGkeEntityProvider = createBackendModule({ + pluginId: 'catalog', + moduleId: 'gcpGkeEntityProvider', + register(env) { + env.registerInit({ + deps: { + config: coreServices.config, + catalog: catalogProcessingExtensionPoint, + logger: coreServices.logger, + scheduler: coreServices.scheduler, + }, + async init({ config, catalog, logger, scheduler }) { + catalog.addEntityProvider( + GkeEntityProvider.fromConfig({ + logger: loggerToWinstonLogger(logger), + scheduler, + config, + }), + ); + }, + }); + }, +}); diff --git a/plugins/catalog-backend-module-gcp/src/module/index.ts b/plugins/catalog-backend-module-gcp/src/module/index.ts new file mode 100644 index 0000000000..c7cd310c97 --- /dev/null +++ b/plugins/catalog-backend-module-gcp/src/module/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { catalogModuleGcpGkeEntityProvider } from './catalogModuleGcpGkeEntityProvider'; diff --git a/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.test.ts b/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.test.ts new file mode 100644 index 0000000000..20e91a85bf --- /dev/null +++ b/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.test.ts @@ -0,0 +1,251 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { GkeEntityProvider } from './GkeEntityProvider'; +import { TaskRunner } from '@backstage/backend-tasks'; +import { + ANNOTATION_KUBERNETES_API_SERVER, + ANNOTATION_KUBERNETES_API_SERVER_CA, + ANNOTATION_KUBERNETES_AUTH_PROVIDER, +} from '@backstage/plugin-kubernetes-common'; +import * as container from '@google-cloud/container'; +import { ConfigReader } from '@backstage/config'; + +describe('GkeEntityProvider', () => { + const clusterManagerClientMock = { + listClusters: jest.fn(), + }; + const connectionMock = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + const taskRunner = { + createScheduleFn: jest.fn(), + run: jest.fn(), + } as TaskRunner; + const schedulerMock = { + createScheduledTaskRunner: jest.fn(), + } as any; + const logger = { + info: jest.fn(), + error: jest.fn(), + }; + let gkeEntityProvider: GkeEntityProvider; + + beforeEach(async () => { + jest.resetAllMocks(); + schedulerMock.createScheduledTaskRunner.mockReturnValue(taskRunner); + gkeEntityProvider = GkeEntityProvider.fromConfigWithClient({ + logger: logger as any, + config: new ConfigReader({ + catalog: { + providers: { + gcp: { + gke: { + parents: ['parent1', 'parent2'], + schedule: { + frequency: { + minutes: 3, + }, + timeout: { + minutes: 3, + }, + }, + }, + }, + }, + }, + }), + scheduler: schedulerMock, + clusterManagerClient: clusterManagerClientMock as any, + }); + await gkeEntityProvider.connect(connectionMock); + }); + + it('should return clusters as Resources', async () => { + clusterManagerClientMock.listClusters.mockImplementation(req => { + if (req.parent === 'parent1') { + return [ + { + clusters: [ + { + name: 'some-cluster', + endpoint: 'http://127.0.0.1:1234', + location: 'some-location', + selfLink: 'http://127.0.0.1/some-link', + masterAuth: { + clusterCaCertificate: 'abcdefg', + }, + }, + ], + }, + ]; + } else if (req.parent === 'parent2') { + return [ + { + clusters: [ + { + name: 'some-other-cluster', + endpoint: 'http://127.0.0.1:5678', + location: 'some-other-location', + selfLink: 'http://127.0.0.1/some-other-link', + masterAuth: { + // no CA cert is ok + }, + }, + ], + }, + ]; + } + + throw new Error(`unexpected parent ${req.parent}`); + }); + await gkeEntityProvider.refresh(); + expect(connectionMock.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities: [ + { + locationKey: 'gcp-gke:some-location', + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Resource', + metadata: { + annotations: { + [ANNOTATION_KUBERNETES_API_SERVER]: 'http://127.0.0.1:1234', + [ANNOTATION_KUBERNETES_API_SERVER_CA]: 'abcdefg', + [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google', + 'backstage.io/managed-by-location': 'gcp-gke:some-location', + 'backstage.io/managed-by-origin-location': + 'gcp-gke:some-location', + }, + name: 'some-cluster', + namespace: 'default', + }, + spec: { + type: 'kubernetes-cluster', + owner: 'unknown', + }, + }, + }, + { + locationKey: 'gcp-gke:some-other-location', + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Resource', + metadata: { + annotations: { + [ANNOTATION_KUBERNETES_API_SERVER]: 'http://127.0.0.1:5678', + [ANNOTATION_KUBERNETES_API_SERVER_CA]: '', + [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google', + 'backstage.io/managed-by-location': + 'gcp-gke:some-other-location', + 'backstage.io/managed-by-origin-location': + 'gcp-gke:some-other-location', + }, + name: 'some-other-cluster', + namespace: 'default', + }, + spec: { + type: 'kubernetes-cluster', + owner: 'unknown', + }, + }, + }, + ], + }); + }); + + const ignoredPartialClustersTests: [ + string, + container.protos.google.container.v1.ICluster, + ][] = [ + [ + 'no-cluster-name', + { + endpoint: 'http://127.0.0.1:1234', + location: 'some-location', + selfLink: 'http://127.0.0.1/some-link', + masterAuth: { + clusterCaCertificate: 'abcdefg', + }, + }, + ], + [ + 'no-self-link', + { + // no selfLink + name: 'some-name', + endpoint: 'http://127.0.0.1:1234', + location: 'some-location', + masterAuth: { + clusterCaCertificate: 'abcdefg', + }, + }, + ], + [ + 'no-endpoint', + { + name: 'some-name', + location: 'some-location', + selfLink: 'http://127.0.0.1/some-link', + masterAuth: { + clusterCaCertificate: 'abcdefg', + }, + }, + ], + [ + 'no-location', + { + name: 'some-name', + endpoint: 'http://127.0.0.1:1234', + selfLink: 'http://127.0.0.1/some-link', + masterAuth: { + clusterCaCertificate: 'abcdefg', + }, + }, + ], + ]; + + it.each(ignoredPartialClustersTests)( + 'ignore cluster - %s', + async (_name, ignoredCluster) => { + clusterManagerClientMock.listClusters.mockImplementation(req => { + if (req.parent === 'parent1') { + return [ignoredCluster]; + } + return [ + { + clusters: [], + }, + ]; + }); + await gkeEntityProvider.refresh(); + expect(connectionMock.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities: [], + }); + }, + ); + + it('should log GKE API errors', async () => { + clusterManagerClientMock.listClusters.mockRejectedValue( + new Error('some-error'), + ); + await gkeEntityProvider.refresh(); + expect(connectionMock.applyMutation).toHaveBeenCalledTimes(0); + expect(logger.error).toHaveBeenCalledTimes(1); + }); +}); diff --git a/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.ts b/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.ts new file mode 100644 index 0000000000..0130955737 --- /dev/null +++ b/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.ts @@ -0,0 +1,223 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + TaskRunner, + readTaskScheduleDefinitionFromConfig, +} from '@backstage/backend-tasks'; +import { + DeferredEntity, + EntityProvider, + EntityProviderConnection, +} from '@backstage/plugin-catalog-node'; + +import { Logger } from 'winston'; +import * as container from '@google-cloud/container'; +import { + ANNOTATION_KUBERNETES_API_SERVER, + ANNOTATION_KUBERNETES_API_SERVER_CA, + ANNOTATION_KUBERNETES_AUTH_PROVIDER, +} from '@backstage/plugin-kubernetes-common'; +import { Config } from '@backstage/config'; +import { SchedulerService } from '@backstage/backend-plugin-api'; + +/** + * Catalog provider to ingest GKE clusters + * + * @public + */ +export class GkeEntityProvider implements EntityProvider { + private readonly logger: Logger; + private readonly scheduleFn: () => Promise; + private readonly gkeParents: string[]; + private readonly clusterManagerClient: container.v1.ClusterManagerClient; + private connection?: EntityProviderConnection; + + private constructor( + logger: Logger, + taskRunner: TaskRunner, + gkeParents: string[], + clusterManagerClient: container.v1.ClusterManagerClient, + ) { + this.logger = logger; + this.scheduleFn = this.createScheduleFn(taskRunner); + this.gkeParents = gkeParents; + this.clusterManagerClient = clusterManagerClient; + } + + public static fromConfig({ + logger, + scheduler, + config, + }: { + logger: Logger; + scheduler: SchedulerService; + config: Config; + }) { + return GkeEntityProvider.fromConfigWithClient({ + logger, + scheduler: scheduler, + config, + clusterManagerClient: new container.v1.ClusterManagerClient(), + }); + } + + public static fromConfigWithClient({ + logger, + scheduler, + config, + clusterManagerClient, + }: { + logger: Logger; + scheduler: SchedulerService; + config: Config; + clusterManagerClient: container.v1.ClusterManagerClient; + }) { + const gkeProviderConfig = config.getConfig('catalog.providers.gcp.gke'); + const schedule = readTaskScheduleDefinitionFromConfig( + gkeProviderConfig.getConfig('schedule'), + ); + return new GkeEntityProvider( + logger, + scheduler.createScheduledTaskRunner(schedule), + gkeProviderConfig.getStringArray('parents'), + clusterManagerClient, + ); + } + + getProviderName(): string { + return `gcp-gke`; + } + + async connect(connection: EntityProviderConnection): Promise { + this.connection = connection; + await this.scheduleFn(); + } + + private filterOutUndefinedDeferredEntity( + e: DeferredEntity | undefined, + ): e is DeferredEntity { + return e !== undefined; + } + + private filterOutUndefinedCluster( + c: container.protos.google.container.v1.ICluster | null | undefined, + ): c is container.protos.google.container.v1.ICluster { + return c !== undefined && c !== null; + } + + private clusterToResource( + cluster: container.protos.google.container.v1.ICluster, + ): DeferredEntity | undefined { + const location = `${this.getProviderName()}:${cluster.location}`; + + if (!cluster.name || !cluster.selfLink || !location || !cluster.endpoint) { + this.logger.warn( + `ignoring partial cluster, one of name=${cluster.name}, endpoint=${cluster.endpoint}, selfLink=${cluster.selfLink} or location=${cluster.location} is missing`, + ); + return undefined; + } + + // TODO fix location type + return { + locationKey: location, + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Resource', + metadata: { + annotations: { + [ANNOTATION_KUBERNETES_API_SERVER]: cluster.endpoint, + [ANNOTATION_KUBERNETES_API_SERVER_CA]: + cluster.masterAuth?.clusterCaCertificate || '', + [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google', + 'backstage.io/managed-by-location': location, + 'backstage.io/managed-by-origin-location': location, + }, + name: cluster.name, + namespace: 'default', + }, + spec: { + type: 'kubernetes-cluster', + owner: 'unknown', + }, + }, + }; + } + + private createScheduleFn(taskRunner: TaskRunner): () => Promise { + return async () => { + const taskId = `${this.getProviderName()}:refresh`; + return taskRunner.run({ + id: taskId, + fn: async () => { + try { + await this.refresh(); + } catch (error) { + this.logger.error(error); + } + }, + }); + }; + } + + private async getClusters(): Promise< + container.protos.google.container.v1.ICluster[] + > { + const clusters = await Promise.all( + this.gkeParents.map(async parent => { + const request = { + parent: parent, + }; + const [response] = await this.clusterManagerClient.listClusters( + request, + ); + return response.clusters?.filter(this.filterOutUndefinedCluster) ?? []; + }), + ); + return clusters.flat(); + } + + async refresh() { + if (!this.connection) { + throw new Error('Not initialized'); + } + + this.logger.info('Discovering GKE clusters'); + + let clusters: container.protos.google.container.v1.ICluster[]; + + try { + clusters = await this.getClusters(); + } catch (e) { + this.logger.error('error fetching GKE clusters', e); + return; + } + const resources = + clusters + .map(c => this.clusterToResource(c)) + .filter(this.filterOutUndefinedDeferredEntity) ?? []; + + this.logger.info( + `Ingesting GKE clusters [${resources + .map(r => r.entity.metadata.name) + .join(', ')}]`, + ); + + await this.connection.applyMutation({ + type: 'full', + entities: resources, + }); + } +} diff --git a/plugins/catalog-backend-module-gcp/src/providers/index.ts b/plugins/catalog-backend-module-gcp/src/providers/index.ts new file mode 100644 index 0000000000..7846afc3ed --- /dev/null +++ b/plugins/catalog-backend-module-gcp/src/providers/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 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 { GkeEntityProvider } from './GkeEntityProvider'; diff --git a/plugins/catalog-backend-module-gcp/src/setupTests.ts b/plugins/catalog-backend-module-gcp/src/setupTests.ts new file mode 100644 index 0000000000..aa70772592 --- /dev/null +++ b/plugins/catalog-backend-module-gcp/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export {}; diff --git a/yarn.lock b/yarn.lock index 3f9b0ee1bc..eec689ba6a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5160,6 +5160,23 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-catalog-backend-module-gcp@workspace:plugins/catalog-backend-module-gcp": + version: 0.0.0-use.local + resolution: "@backstage/plugin-catalog-backend-module-gcp@workspace:plugins/catalog-backend-module-gcp" + dependencies: + "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-tasks": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" + "@backstage/plugin-kubernetes-common": "workspace:^" + "@google-cloud/container": ^4.15.0 + winston: ^3.2.1 + languageName: unknown + linkType: soft + "@backstage/plugin-catalog-backend-module-gerrit@workspace:plugins/catalog-backend-module-gerrit": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-backend-module-gerrit@workspace:plugins/catalog-backend-module-gerrit" @@ -10912,12 +10929,12 @@ __metadata: languageName: node linkType: hard -"@google-cloud/container@npm:^4.0.0": - version: 4.13.0 - resolution: "@google-cloud/container@npm:4.13.0" +"@google-cloud/container@npm:^4.0.0, @google-cloud/container@npm:^4.15.0": + version: 4.15.0 + resolution: "@google-cloud/container@npm:4.15.0" dependencies: google-gax: ^3.5.8 - checksum: 21e57e8c69e2df8cf6899541dc405f9a6c05d999289cf211c2bb8dc6e33bf7ef36ff255cfde75c6448dd335fb5b707e77f5df49bee561decbd5ed398c58db6f6 + checksum: e81f1b708ab44c55b8e998d34c442baed10a113828ef51b4a84b806a2b04a10a0dc40ee0aa202386ac6158b34b9d3f03467d7008da9e4f709fdb1f021e725b8c languageName: node linkType: hard From b2ccddefbdc6dd41e3d4999ff3bc0034ddadaa5e Mon Sep 17 00:00:00 2001 From: rui ma Date: Fri, 28 Jul 2023 17:05:48 +0800 Subject: [PATCH 27/64] fix: remove sonar card disable class Signed-off-by: rui ma --- .changeset/large-vans-cross.md | 5 +++++ .../src/components/SonarQubeCard/SonarQubeCard.tsx | 6 ------ 2 files changed, 5 insertions(+), 6 deletions(-) create mode 100644 .changeset/large-vans-cross.md diff --git a/.changeset/large-vans-cross.md b/.changeset/large-vans-cross.md new file mode 100644 index 0000000000..f50b432742 --- /dev/null +++ b/.changeset/large-vans-cross.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-sonarqube': patch +--- + +Remove sonarQube card disable class diff --git a/plugins/sonarqube/src/components/SonarQubeCard/SonarQubeCard.tsx b/plugins/sonarqube/src/components/SonarQubeCard/SonarQubeCard.tsx index 7ad4717bd2..46a22b9284 100644 --- a/plugins/sonarqube/src/components/SonarQubeCard/SonarQubeCard.tsx +++ b/plugins/sonarqube/src/components/SonarQubeCard/SonarQubeCard.tsx @@ -68,9 +68,6 @@ const useStyles = makeStyles(theme => ({ lastAnalyzed: { color: theme.palette.text.secondary, }, - disabled: { - backgroundColor: theme.palette.background.default, - }, })); /** @public */ @@ -163,9 +160,6 @@ export const SonarQubeCard = (props: { action: classes.action, }, }} - className={ - !loading && (!projectTitle || !value) ? classes.disabled : undefined - } > {loading && } From d5a185bf46e819f04494e22724b1715cefe4058e Mon Sep 17 00:00:00 2001 From: Yor Jaggy Date: Tue, 25 Jul 2023 10:29:24 -0500 Subject: [PATCH 28/64] Update ADOPTERS.md Adding Platzi as adopter Signed-off-by: Yor Jaggy Signed-off-by: yorjaggy --- ADOPTERS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index cd340fd5f9..c14d669b12 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -254,4 +254,5 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Celonis](https://celonis.com) | [@georgeyord](https://github.com/georgeyord), [@LauraMoraB](https://github.com/LauraMoraB), [@mariosant](https://github.com/mariosant), [@Rbillon59](https://github.com/Rbillon59) | Internal developer and product portal implementation. | | [Rabobank](https://www.rabobank.com) | [Willem Dekker](https://github.com/wdekker), [Sridhar Gnanasekaran](https://github.com/srid99), [Ruben Ernst](https://github.com/Ruben-E) | At Rabobank, our mission is to make life better for engineers. After exploring various options in the market, we've discovered that Backstage provides the perfect foundation for creating a platform that meets our engineers' needs and helps us achieve our goals. We want to make our engineers happy by offering standardized services, freeing them up to focus on delivering value. Our vision is to create a one-stop platform where engineers can find everything related to software and services. In practice, we're excited about using all the cool features in Backstage, and we're even planning to build our own plugins. | | [JB Hi-Fi](https://www.jbhifi.com.au) | [@bahman](https://github.com/bahman-jb/) | We use Backstage as a central place for all of our components/apis across multiple teams. It helps us to quickly identify the ownership of a component/api, find the related links and have a quick access to CI/CD pipelines. | -| [Zenklub](https://www.zenklub.com.br) | [@zenklub](https://github.com/zenklub), [@gioufop](https://github.com/gioufop) | Developer portal, services catalog and centralization of metrics from Grafana Stack and AWS. Furthermore, centralization of documentation and infra details like Tools, Network services and so on. | \ No newline at end of file +| [Zenklub](https://www.zenklub.com.br) | [@zenklub](https://github.com/zenklub), [@gioufop](https://github.com/gioufop) | Developer portal, services catalog and centralization of metrics from Grafana Stack and AWS. Furthermore, centralization of documentation and infra details like Tools, Network services and so on. | +| [Platzi](https://platzi.com/) | [@juancarestre](https://github.com/juancarestre/), [Engineering at Platzi](engineering@platzi.com) | Backstage allow our developers to get easily engaged with all the internal components, technical documentations and software templates. All new developers reduce its onboarding time via Backstage, and after a couple of integrations it allowed us to create new components with in a couple of clicks. | From 25261445de3ad0cfe95eff56407fff2ddac05155 Mon Sep 17 00:00:00 2001 From: yorjaggy Date: Tue, 25 Jul 2023 15:38:09 -0500 Subject: [PATCH 29/64] updating not reachable link Signed-off-by: yorjaggy --- ADOPTERS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index c14d669b12..9c76b01ff3 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -255,4 +255,4 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Rabobank](https://www.rabobank.com) | [Willem Dekker](https://github.com/wdekker), [Sridhar Gnanasekaran](https://github.com/srid99), [Ruben Ernst](https://github.com/Ruben-E) | At Rabobank, our mission is to make life better for engineers. After exploring various options in the market, we've discovered that Backstage provides the perfect foundation for creating a platform that meets our engineers' needs and helps us achieve our goals. We want to make our engineers happy by offering standardized services, freeing them up to focus on delivering value. Our vision is to create a one-stop platform where engineers can find everything related to software and services. In practice, we're excited about using all the cool features in Backstage, and we're even planning to build our own plugins. | | [JB Hi-Fi](https://www.jbhifi.com.au) | [@bahman](https://github.com/bahman-jb/) | We use Backstage as a central place for all of our components/apis across multiple teams. It helps us to quickly identify the ownership of a component/api, find the related links and have a quick access to CI/CD pipelines. | | [Zenklub](https://www.zenklub.com.br) | [@zenklub](https://github.com/zenklub), [@gioufop](https://github.com/gioufop) | Developer portal, services catalog and centralization of metrics from Grafana Stack and AWS. Furthermore, centralization of documentation and infra details like Tools, Network services and so on. | -| [Platzi](https://platzi.com/) | [@juancarestre](https://github.com/juancarestre/), [Engineering at Platzi](engineering@platzi.com) | Backstage allow our developers to get easily engaged with all the internal components, technical documentations and software templates. All new developers reduce its onboarding time via Backstage, and after a couple of integrations it allowed us to create new components with in a couple of clicks. | +| [Platzi](https://platzi.com/) | [@juancarestre](https://github.com/juancarestre/), [Engineering at Platzi](https://github.com/PlatziDev/) | Backstage allow our developers to get easily engaged with all the internal components, technical documentations and software templates. All new developers reduce its onboarding time via Backstage, and after a couple of integrations it allowed us to create new components with in a couple of clicks. | From 5cf35d194818a4bb72db158e94d569c4ca8ec5dc Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Fri, 28 Jul 2023 14:22:57 +0200 Subject: [PATCH 30/64] Remove shared environment code & documentation Signed-off-by: Philipp Hugenroth --- .../architecture/02-backends.md | 2 +- .../building-backends/01-index.md | 32 ------ .../blog/2023-02-15-backend-system-alpha.mdx | 1 - .../src/CreateBackend.test.ts | 82 +++++--------- .../backend-defaults/src/CreateBackend.ts | 22 ---- .../wiring/createSharedEnvironment.test.ts | 100 ----------------- .../src/wiring/createSharedEnvironment.ts | 102 ------------------ .../backend-plugin-api/src/wiring/index.ts | 5 - 8 files changed, 26 insertions(+), 320 deletions(-) delete mode 100644 packages/backend-plugin-api/src/wiring/createSharedEnvironment.test.ts delete mode 100644 packages/backend-plugin-api/src/wiring/createSharedEnvironment.ts diff --git a/docs/backend-system/architecture/02-backends.md b/docs/backend-system/architecture/02-backends.md index b6079d71b4..70ea8f7836 100644 --- a/docs/backend-system/architecture/02-backends.md +++ b/docs/backend-system/architecture/02-backends.md @@ -37,4 +37,4 @@ At a high level, when you call `createBackend`, it will create a new backend ins Underneath the hood, `createBackend` calls `createSpecializedBackend` from `@backstage/backend-app-api` which is responsible for actually creating the backend instance, but with no services or no features. You can think of `createBackend` more of a 'batteries included' approach, and `createSpecializedBackend` a little more low level. -As mentioned previously there's also the ability to create multiple of these backends in your project so that you can split apart your backend and deploy different backends that can scale independently of each other. For instance you might choose to deploy a backend with only the catalog plugin enabled, and one with just the scaffolder plugin enabled. We've provided some tools to be able to share services and defaults across your backend system, and you can find out more about that in the [shared environments docs](../building-backends/01-index.md#shared-environments). +As mentioned previously there's also the ability to create multiple of these backends in your project so that you can split apart your backend and deploy different backends that can scale independently of each other. For instance you might choose to deploy a backend with only the catalog plugin enabled, and one with just the scaffolder plugin enabled. diff --git a/docs/backend-system/building-backends/01-index.md b/docs/backend-system/building-backends/01-index.md index 66bf68ab07..0f23ae0006 100644 --- a/docs/backend-system/building-backends/01-index.md +++ b/docs/backend-system/building-backends/01-index.md @@ -148,35 +148,3 @@ backend.start(); ``` We've now split the backend into two separate deployments, but we still need to make sure that they can communicate with each other. This is the hard and somewhat tedious part, as Backstage currently doesn't provide an out of the box solution that solves this. You'll need to manually configure the two backends with custom implementations of the `DiscoveryService` and have them return the correct URLs for each other. Likewise, you'll also need to provide a custom implementation of the `DiscoveryApi` in the frontend, unless you surface the two backends via a proxy that handles the routing instead. - -### Shared Environments - -To make it a bit easier to manage multiple backends, it's possible to create a shared environment that can be used across multiple backends. You would typically house it in a separate package that can be referenced by backends in your monorepo, or published to a package registry for broader use. - -A shared environment contains a set of service implementations that should be used across all backends. These services will override the default ones, but if a service is provided directly to the backend, it will override the one in the shared environment. - -A shared environment is defined using `createSharedEnvironment`. In this example we place it in a new and separate package called `backend-env`: - -```ts -// packages/backend-env/src/index.ts -import { createSharedEnvironment } from '@backstage/backend-plugin-api'; -import { customDiscoveryServiceFactory } from './customDiscoveryServiceFactory'; - -export const env = createSharedEnvironment({ - services: [ - customDiscoveryServiceFactory(), // custom DiscoveryService implementation - ], -}); -``` - -And passed on to backends using the `env` option: - -```ts -// packages/backend-b/src/index.ts, imports omitted -import { env } from '@internal/backend-env'; - -const backend = createBackend({ env }); - -backend.add(scaffolderPlugin()); -backend.start(); -``` diff --git a/microsite/blog/2023-02-15-backend-system-alpha.mdx b/microsite/blog/2023-02-15-backend-system-alpha.mdx index 2aef5596a0..28ff7bdf6b 100644 --- a/microsite/blog/2023-02-15-backend-system-alpha.mdx +++ b/microsite/blog/2023-02-15-backend-system-alpha.mdx @@ -26,7 +26,6 @@ When we set out on this project, we had a few primary goals. First, we intended - Make it easier to create and maintain backend installations. - Align how plugins provide points of customization and how those customizations are installed. - Make it much easier to maintain plugins, in particular keeping the API stable. -- Simplify the process of splitting plugins out into separate deployments with shared environments. - Improve the local development and testing experience. Prioritizing simplicity is often a guiding principle that we use for designs that span multiple ownership roles. We decide on which parts of the system that we think are the most important to have as simple as possible, or, viewed from the opposite end, where in the system we put necessary complexity. In this case, we optimized for keeping the backend setup as simple as possible, followed by modules and plugins, then libraries, and lastly the framework itself. What this guidance means in practice is that when there is complexity that needs to be added to implement a certain feature, we place as much of it as possible within the framework itself, then libraries, plugins and modules, and if absolutely needed, the backend setup. diff --git a/packages/backend-defaults/src/CreateBackend.test.ts b/packages/backend-defaults/src/CreateBackend.test.ts index 946de90b2b..88e6bddd97 100644 --- a/packages/backend-defaults/src/CreateBackend.test.ts +++ b/packages/backend-defaults/src/CreateBackend.test.ts @@ -19,10 +19,9 @@ import { createBackendPlugin, createServiceFactory, createServiceRef, - createSharedEnvironment, } from '@backstage/backend-plugin-api'; -import { mockServices } from '@backstage/backend-test-utils'; import { createBackend } from './CreateBackend'; +import { mockServices } from '@backstage/backend-test-utils'; const fooServiceRef = createServiceRef({ id: 'foo', scope: 'root' }); const barServiceRef = createServiceRef({ id: 'bar', scope: 'root' }); @@ -86,65 +85,34 @@ describe('createBackend', () => { ).toThrow('The core.pluginMetadata service cannot be overridden'); }); - it('should throw if an unsupported InternalSharedEnvironment version is passed in', () => { - expect(() => - createBackend({ - env: {} as any, - }), - ).toThrow( - "Shared environment version 'undefined' is invalid or not supported", - ); - expect(() => - createBackend({ - env: { version: {} } as any, - }), - ).toThrow( - "Shared environment version '[object Object]' is invalid or not supported", - ); - expect(() => - createBackend({ - env: { version: 'v2' } as any, - }), - ).toThrow("Shared environment version 'v2' is invalid or not supported"); - }); - it('should prioritize services correctly', async () => { const backend = createBackend({ - env: createSharedEnvironment({ - services: [ - createServiceFactory({ - service: coreServices.rootHttpRouter, - deps: {}, - async factory() { - return { - use() {}, - }; - }, - }), - mockServices.config.factory({ - data: { root: 'root-env' }, - }), - createServiceFactory({ - service: fooServiceRef, - deps: {}, - async factory() { - return 'foo-env'; - }, - }), - createServiceFactory({ - service: barServiceRef, - deps: {}, - async factory() { - return 'bar-env'; - }, - }), - ], - })(), services: [ + createServiceFactory({ + service: coreServices.rootHttpRouter, + deps: {}, + async factory() { + return { + use() {}, + }; + }, + }), + mockServices.config.factory({ + data: { root: 'root-backend' }, + }), createServiceFactory({ service: fooServiceRef, deps: {}, - factory: async () => 'foo-backend', + async factory() { + return 'foo-backend'; + }, + }), + createServiceFactory({ + service: barServiceRef, + deps: {}, + async factory() { + return 'bar-backend'; + }, }), ], }); @@ -161,9 +129,9 @@ describe('createBackend', () => { bar: barServiceRef, }, async init({ config, foo, bar }) { - expect(config.get('root')).toBe('root-env'); + expect(config.get('root')).toBe('root-backend'); expect(foo).toBe('foo-backend'); - expect(bar).toBe('bar-env'); + expect(bar).toBe('bar-backend'); }, }); }, diff --git a/packages/backend-defaults/src/CreateBackend.ts b/packages/backend-defaults/src/CreateBackend.ts index 480f365216..7051b58ae5 100644 --- a/packages/backend-defaults/src/CreateBackend.ts +++ b/packages/backend-defaults/src/CreateBackend.ts @@ -36,13 +36,8 @@ import { import { ServiceFactory, ServiceFactoryOrFunction, - SharedBackendEnvironment, } from '@backstage/backend-plugin-api'; -// Internal import of the type to avoid needing to export this. -// eslint-disable-next-line @backstage/no-forbidden-package-imports -import type { InternalSharedBackendEnvironment } from '@backstage/backend-plugin-api/src/wiring/createSharedEnvironment'; - export const defaultServiceFactories = [ cacheServiceFactory(), configServiceFactory(), @@ -65,7 +60,6 @@ export const defaultServiceFactories = [ * @public */ export interface CreateBackendOptions { - env?: SharedBackendEnvironment; services?: ServiceFactoryOrFunction[]; } @@ -81,22 +75,6 @@ export function createBackend(options?: CreateBackendOptions): Backend { ); services.push(...providedServices); - // Middle priority: Services from the shared environment - if (options?.env) { - const env = options.env as unknown as InternalSharedBackendEnvironment; - if (env.version !== 'v1') { - throw new Error( - `Shared environment version '${env.version}' is invalid or not supported`, - ); - } - - const environmentServices = - env.services?.filter( - sf => !services.some(({ service }) => sf.service.id === service.id), - ) ?? []; - services.push(...environmentServices); - } - // Lowest priority: Default services that are not already provided by environment or directly to createBackend const defaultServices = defaultServiceFactories.filter( sf => !services.some(({ service }) => service.id === sf.service.id), diff --git a/packages/backend-plugin-api/src/wiring/createSharedEnvironment.test.ts b/packages/backend-plugin-api/src/wiring/createSharedEnvironment.test.ts deleted file mode 100644 index 381a4799d2..0000000000 --- a/packages/backend-plugin-api/src/wiring/createSharedEnvironment.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - createServiceFactory, - createServiceRef, - ServiceFactoryOrFunction, -} from '../services'; -import { - createSharedEnvironment, - InternalSharedBackendEnvironment, -} from './createSharedEnvironment'; - -const fooService = createServiceRef({ id: 'foo', scope: 'root' }); -const fooFactory = createServiceFactory({ - service: fooService, - deps: {}, - async factory() { - return 'foo'; - }, -}); - -const barService = createServiceRef({ id: 'bar', scope: 'root' }); -const barFactory = createServiceFactory({ - service: barService, - deps: {}, - async factory() { - return 0xba5; - }, -}); - -describe('createSharedEnvironment', () => { - it('should create an empty shared environment', () => { - const env = createSharedEnvironment({}); - expect(env).toBeDefined(); - const internalEnv = env() as unknown as InternalSharedBackendEnvironment; - expect(internalEnv).toEqual({ - $$type: '@backstage/SharedBackendEnvironment', - version: 'v1', - services: undefined, - }); - }); - - it('should create a shared environment with services', () => { - const env = createSharedEnvironment({ - services: [fooFactory, barFactory()], - }); - const internalEnv = env() as unknown as InternalSharedBackendEnvironment; - expect(internalEnv.version).toBe('v1'); - expect(internalEnv.services?.length).toBe(2); - expect(internalEnv.services?.[0]?.service.id).toBe('foo'); - expect(internalEnv.services?.[1]?.service.id).toBe('bar'); - }); - - it('should create a shared environment with options', () => { - const env = createSharedEnvironment((options?: { withFoo?: boolean }) => { - const services = new Array(); - if (options?.withFoo) { - services.push(fooFactory()); - } - services.push(barFactory); - return { services }; - }); - const internalEnv1 = env() as unknown as InternalSharedBackendEnvironment; - expect(internalEnv1.version).toBe('v1'); - expect(internalEnv1.services?.length).toBe(1); - expect(internalEnv1.services?.[0]?.service.id).toBe('bar'); - - const internalEnv2 = env({ - withFoo: true, - }) as unknown as InternalSharedBackendEnvironment; - expect(internalEnv2.version).toBe('v1'); - expect(internalEnv2.services?.length).toBe(2); - expect(internalEnv2.services?.[0]?.service.id).toBe('foo'); - expect(internalEnv2.services?.[1]?.service.id).toBe('bar'); - }); - - it('should not allow duplicate service factories', () => { - expect(() => - createSharedEnvironment({ - services: [fooFactory, fooFactory()], - })(), - ).toThrow( - "Duplicate service implementations provided in shared environment for 'foo'", - ); - }); -}); diff --git a/packages/backend-plugin-api/src/wiring/createSharedEnvironment.ts b/packages/backend-plugin-api/src/wiring/createSharedEnvironment.ts deleted file mode 100644 index 57f0533523..0000000000 --- a/packages/backend-plugin-api/src/wiring/createSharedEnvironment.ts +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ServiceFactory, ServiceFactoryOrFunction } from '../services'; - -/** - * The configuration options passed to {@link createSharedEnvironment}. - * - * @public - */ -export interface SharedBackendEnvironmentConfig { - services?: ServiceFactoryOrFunction[]; -} - -/** - * An opaque type that represents the contents of a shared backend environment. - * - * @public - */ -export interface SharedBackendEnvironment { - $$type: '@backstage/SharedBackendEnvironment'; - - // NOTE: This type is opaque in order to allow for future API evolution without - // cluttering the external API. For example we might want to add support - // for more powerful callback based backend modifications. - // - // By making this opaque we also ensure that the type doesn't become an input - // type that we need to care about, as it would otherwise be possible to pass - // a custom environment definition to `createBackend`, which we don't want. -} - -/** - * This type is NOT supposed to be used by anyone except internally by the - * backend-app-api package. - * - * @internal - */ -export interface InternalSharedBackendEnvironment { - version: 'v1'; - services?: ServiceFactory[]; -} - -/** - * Creates a shared backend environment which can be used to create multiple - * backends. - * - * @public - */ -export function createSharedEnvironment< - TOptions extends [options?: object] = [], ->( - config: - | SharedBackendEnvironmentConfig - | ((...params: TOptions) => SharedBackendEnvironmentConfig), -): (...options: TOptions) => SharedBackendEnvironment { - const configCallback = typeof config === 'function' ? config : () => config; - - return (...options) => { - const actualConfig = configCallback(...options); - const services = actualConfig?.services?.map(sf => - typeof sf === 'function' ? sf() : sf, - ); - - const exists = new Set(); - const duplicates = new Set(); - for (const { service } of services ?? []) { - if (exists.has(service.id)) { - duplicates.add(service.id); - } else { - exists.add(service.id); - } - } - - if (duplicates.size > 0) { - const dupStr = [...duplicates].map(id => `'${id}'`).join(', '); - throw new Error( - `Duplicate service implementations provided in shared environment for ${dupStr}`, - ); - } - - // Here to ensure type safety in this internal implementation. - const env: SharedBackendEnvironment & InternalSharedBackendEnvironment = { - $$type: '@backstage/SharedBackendEnvironment', - version: 'v1', - services, - }; - return env; - }; -} diff --git a/packages/backend-plugin-api/src/wiring/index.ts b/packages/backend-plugin-api/src/wiring/index.ts index 242301ea4d..9cb767b8ac 100644 --- a/packages/backend-plugin-api/src/wiring/index.ts +++ b/packages/backend-plugin-api/src/wiring/index.ts @@ -14,11 +14,6 @@ * limitations under the License. */ -export { createSharedEnvironment } from './createSharedEnvironment'; -export type { - SharedBackendEnvironment, - SharedBackendEnvironmentConfig, -} from './createSharedEnvironment'; export type { BackendModuleConfig, BackendPluginConfig, From d008aefef8089e9aaeda25839ed7dd5df55aabf6 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Fri, 28 Jul 2023 14:48:38 +0200 Subject: [PATCH 31/64] Build api-reports & add changeset Signed-off-by: Philipp Hugenroth --- .changeset/rich-zoos-occur.md | 6 ++++++ packages/backend-defaults/api-report.md | 3 --- packages/backend-plugin-api/api-report.md | 21 --------------------- 3 files changed, 6 insertions(+), 24 deletions(-) create mode 100644 .changeset/rich-zoos-occur.md diff --git a/.changeset/rich-zoos-occur.md b/.changeset/rich-zoos-occur.md new file mode 100644 index 0000000000..65d21710d1 --- /dev/null +++ b/.changeset/rich-zoos-occur.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-plugin-api': minor +'@backstage/backend-defaults': minor +--- + +Removing shared environments concept from the new experimental backend system. diff --git a/packages/backend-defaults/api-report.md b/packages/backend-defaults/api-report.md index 07218f610e..6cc58de663 100644 --- a/packages/backend-defaults/api-report.md +++ b/packages/backend-defaults/api-report.md @@ -5,15 +5,12 @@ ```ts import { Backend } from '@backstage/backend-app-api'; import { ServiceFactoryOrFunction } from '@backstage/backend-plugin-api'; -import { SharedBackendEnvironment } from '@backstage/backend-plugin-api'; // @public (undocumented) export function createBackend(options?: CreateBackendOptions): Backend; // @public (undocumented) export interface CreateBackendOptions { - // (undocumented) - env?: SharedBackendEnvironment; // (undocumented) services?: ServiceFactoryOrFunction[]; } diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index b3cbef993c..093b4acec4 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -222,15 +222,6 @@ export function createServiceRef( config: ServiceRefConfig, ): ServiceRef; -// @public -export function createSharedEnvironment< - TOptions extends [options?: object] = [], ->( - config: - | SharedBackendEnvironmentConfig - | ((...params: TOptions) => SharedBackendEnvironmentConfig), -): (...options: TOptions) => SharedBackendEnvironment; - // @public export interface DatabaseService { getClient(): Promise; @@ -476,18 +467,6 @@ export interface ServiceRefConfig { scope?: TScope; } -// @public -export interface SharedBackendEnvironment { - // (undocumented) - $$type: '@backstage/SharedBackendEnvironment'; -} - -// @public -export interface SharedBackendEnvironmentConfig { - // (undocumented) - services?: ServiceFactoryOrFunction[]; -} - // @public export interface TokenManagerService { authenticate(token: string): Promise; From 7f5e3b6a185f4e0d7f6a7e67f82fafc3fdd4d664 Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 28 Jul 2023 15:03:01 +0200 Subject: [PATCH 32/64] feat: added a blog post for frontside Signed-off-by: blam --- .../2023-07-28-frontside-adopter-post.mdx | 51 ++++++++++++++++++ microsite/blog/assets/2023-07-28/header.png | Bin 0 -> 58031 bytes 2 files changed, 51 insertions(+) create mode 100644 microsite/blog/2023-07-28-frontside-adopter-post.mdx create mode 100644 microsite/blog/assets/2023-07-28/header.png diff --git a/microsite/blog/2023-07-28-frontside-adopter-post.mdx b/microsite/blog/2023-07-28-frontside-adopter-post.mdx new file mode 100644 index 0000000000..c6f3cba403 --- /dev/null +++ b/microsite/blog/2023-07-28-frontside-adopter-post.mdx @@ -0,0 +1,51 @@ +--- +# prettier-ignore +title: "Five common traits of successful Backstage adopters" +author: Taras Mankovski, CXO, Frontside +--- + +![backstage header](assets/2023-07-28/header.png) + +At [Frontside](https://frontside.com/), our goal is to help cloud-native teams create cohesive development experiences. We've been at the Backstage party since the beginning helping companies adopt and extend Backstage to fit the needs of their unique ecosystems. + +Through our experience, we've uncovered five common traits of organizations that have improved developer experience through successful Backstage implementation and adoption. In this blog, we'll dive into these traits with some thoughts on how you can follow their blueprint. + +{/* truncate */} + +## **1) They take a product-centric approach** + +The biggest factor for successful adopters of Backstage is their approach. There is a high correlation of success within organizations that approach building a developer portal like they're building a product. Some organizations will jump straight into solutioning instead of taking their time to identify what the problems are, how they affect users, and how those issues can be addressed. + +The team building out Backstage should assess or work from existing insights on the core challenges that developers are facing in their organization — both qualitative and quantitative data counts here — and connect those issues to a product roadmap with initial use cases and adoption milestones marked. By doing their research in advance, successful orgs have a clear vision of what they want to accomplish with their Backstage instance. They know exactly what goals need to be met and have pressure-tested potential issues and barriers. + +If you've already started your Backstage adoption journey but haven't mapped a Product Requirements Document (or PRD), don't panic! There is always time to do the foundational knowledge-building necessary to deploy something successful. + +## **2) They tie Backstage to a larger platform journey** + +Platform engineering has emerged in response to the growing complexity of cloud-native software architecture. Backstage has a huge role to play in implementing a platform strategy but its part of a larger story. Having buy-in from both higher ups and the teams tasked with platform goals is an essential step in successful Backstage adoption. Without the support at the top, it can be difficult to implement platforming as a priority within your organization. + +And by "the top", we don't necessarily mean your CTO needs to rubber stamp your Backstage POC. But it's important that higher-level Platform goals have been set among VPs and directors with the understanding that a well-constructed developer portal can be the catalyst to achieving not only better developer experience but broader cultural shifts. + +Having this kind of executive buy-in means more than just getting stakeholders on board with a portal build or clearing pathways towards Backstage adoption; it also means ensuring they understand the realities of your organization's journey. + +## **3) They prioritize developer experience** + +Having a dedicated developer experience or DevOps team tasked not only with building out a Backstage instance — but with leading adoption — is another big factor to success. + +Competing platforms are very common at big companies, with many teams doing different things or serving different parts of a business. But developer experience teams usually have a very clear mandate and are often one of the only commonalities between these competing teams. This team should have the bandwidth available to oversee the adoption process from start to finish, and partner with the right folks (research, design, internal comms and marketing) to create an effective adoption plan tailored specifically for a company's needs. + +If you don't have ‌established developer experience resources in place, then the questions the team leading the Backstage work needs to ask are "what are the common problems to be solved at a platform level?" and "once we've proved our initial use cases, how are we internally evangelizing this amongst our competing teams?" + +## **4) They collaborate with other platform teams** + +A developer experience team can not single-handedly transform an organization's development structures, processes, and culture. It requires collaboration with teams responsible for other aspects of the platform. Successful adopters collaborate with other teams to bring them along on the journey of improving developer experience. They use Backstage as an opportunity to foster their inner-source contribution practice by allowing multiple platform teams to contribute plugins to their portal. + +The developer experience team takes on the role of host in that collaborative process. They support other teams in integrating their features into the portal, provide guidance on best practices, and advocate for the needs of developers. + +## **5) They engage, learn, and grow with the community** + +One of the key benefits of building a developer platform based off an open source framework is leveraging the contributions and best practices from the community. The Backstage community is the largest gathering of developer experience practitioners in the world. There are now [thousands of adopters](https://github.com/backstage/backstage/blob/63aa3a65ec05f638ba76c06878d6635194620b34/ADOPTERS.md#L4) with a wealth of experience and best practices to learn from — and they are excited to share! + +By participating in the community, developer experience leaders can learn from their peers at other companies about strategies that work and can keep up with the latest features of the ever-evolving Backstage platform. They're aware of what solutions exist within the open source framework and plugin marketplace as well as what they may need to build in-house. + +In short, successful Backstage adopters engage within the Backstage community and their peer group to find solutions that fit their common challenges as well as their unique needs. diff --git a/microsite/blog/assets/2023-07-28/header.png b/microsite/blog/assets/2023-07-28/header.png new file mode 100644 index 0000000000000000000000000000000000000000..69036b60384d215612fb79ec0a97ee1ec770d1ec GIT binary patch literal 58031 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

8_s0Ma|ck_40wP(BC_sLHrKjh?S!O`2LOz@r1 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 eajJzmaXQ){coIa#@VvdJ>gs+dfs&k&8Jpq{ zIuQ{Oj3k$|9cQit3;h$Iby=&i_y&xIq?3>V9*nOr7fh=w4?WS4aO8QHOmjIk3cZiI zv0YZ3$5A$khtvzUpC5Q)O>R4t-z;$w8+e;<9t(ee0<>%A>5(FnLY(yT=g*6Ekd5O+ zVSvctp7FKfa;N9L>f?gMuyfVme%nIJ1sN*;QdF&7{8u`Mq{qS=`y`v{>edeGEhD3w zASefKZ%n>HQ&D*)bUwaYbUA=i(wmLE?AO3`fL1{`*hlxehiTbq9d~FYnx#ykD4+do zy0@8l`WRF~WS3XMb24{Iasp5S&z?QYgVZDTviYyacIL1q7Vg2(<>CfAC^A>4uGMA? zG`G++p>ytx7W!cE zr_8^2+^sxa4xxjNzp+uJf6wuT6U^wiUtUt;i3@G6INOlrS&`;~W{nxTi$inW!!c0I z6clh_uve_r(&iw~8r)%qTQ41FrW}m)7`hE4=T9(1k)IYj`)P*NszY`{;WZ4m>S_%^ zcrZAY!ME(}(Ur{nBJw6HLE9?0V{mPj`m+&waM{`w_EWcgN#jk>^XghcavdWfnhbR-QomcJRJ5bE2Qmv{G-Op-0fSyPNG zA#4wJ_tz#vd@Y|}oEH^StvsL()xC({(mrTsjQT!`rYNfc-k#pvw-5sA-FJ)T4)Jq! zHU)WCn$F5~)=d5!W?H`~wcuHOnc}ma02d*qAgqK02eWMFWhdMe-mdA$z24+qo9Qlt z4B4C-I=@N$i&mrmM*8lO_#pr~I9P_v=a586c-s)NSeyI$ls9*%j%so^6wM|#_c4HX zeN-HeJap)QY2YeSI&?hv$vgBWzu%O6Kb)jg1~K-?7SRRV`~E;A;fo@911*{vY`od< zYG+@zd$JbqN|YOKkC(*YE59UKSFuxV$+mnH#irh!Jx985`Is6q2H>H#FjIaJ`}H@~ zlFI;80ors)_lZ>9#pEbVD3GK|ZCx-m-KTKdFJk*E{;p!ly0LxSP-zW=@1`m@n-k+E z^J!42yPJQesJ#ro%8}(N(AZI(0QwqMZKZOAN;Bq}yH+v9t7>Sp0?&UV2V&3?*g^Gv zAH4G0PJZ`@BeS(+1Y;-L;6x&ndIeZa_r?Dv_H2z}y9I}ltKY9tUETJx*?k!$CM0Wjk)+$F9AUhZAKWq?;?ZfX$$TjxhBuebEw z4^8WL%_pg6a~+l{X>gCJ{;@mD%ORYB6UdkswvN69vh08ilqRX20 z*N7{hE?<;wAi2^+D^rj`<>8%1qWrfyDGZ+55fx##+ zR|vUQT;$>9;o~61YWDN(Ulk@)}TaZ2w12rr?nAZayGJ1KM!BeApyy8vXy^8a=%B*^o#lC-P)1I+} zZSsZIDRPeYE({Y2E$6WlYKuYU(LVUPeLLrRtR)x1z~>ADmlLvpOr$mmyvS$rF;~ro z>TPnxMy?$8ZHJVd1wm}n;)3!)USwsQK=LeKhDi4EFft#4AW-~qJ+NvJ0_Aydp_ zyFInmqBpn?{Y#l-yR9_l(0q)uPa-W4vA58x3HuQp587U3k9ErMw z;*7mTh3G zD~!t?Df+Y4!4#)ZG+X>*D-oKQQS}UX>v&4pLS){_Y%fX!2gp0VsIig|HUSXm@e)y& zY*85}y7Hl#LX3gKgGr{SBYKgwX{kYm(c$DaKcBnp%y|N(_pcbv#pC2Z%^<^|(6W6} z+>1O+4cw}zbs~H(N9SXzq5&0dSmSsK&y*5h*JFJ4=Y5lbNO4jqN+AaD91@u}Myf&X z^*A!^;L%14nf%Wnvcgh=&dT$7=CNsMOjUag9Mhxdkp4Y=HC z5H|gCD)d`z<~byH7f?Met0_s)Al--Txiz*d5iTrC7GE*)I*Oo)kft@v3p5rj#cTnM zjgK%uEJpWM2kEdFgpiR`3P#fIWs|It0zZFg%19#~MssNGjEX(C^emMVtdgSHedv!L=(oafv_r?gkF6U#BoUI>-;v7G< z0#~N8vT{$Q+$EtFE9&o=nfKt)+Kwwf2b{`)a)*c(fEd=Wff#AisfuplP7iCJgOHxZ zdaCB5f#|JL*xG8w*qXBAH(Qcime=H4^lj_WR^!J1Qr z+RbST^b9>wQ8Vv}K@?vJ0m~g43Hc~QAIg0$|k89Ze*EMlpEYO3N5i?S)Th3sJlbo5AE?|phWQT@eg^L zt*L#Uu|5P`4U!!wbX-EAqko;K1+rVolw?fh5pMAVBS?9X?#` z(0|<|`i|6uJ>zjrZ8tY--oLr-Yx*~VU9l|Y3{-<_vkCB1KIre*E4Q9PmmX9H9VzW~$(N+*a?0)|I3He5|=hEqjUQr@W185+gDl0<} z<8-gG;tV1;1f%%GbhzS}VhA#@q?n?U6&`|5dYDx^W)rK_g>+r|8F&5xv7#ac@>b$) zW@C)4;SZPdj@ik6r#r;Y=H`LEW^r+G=YSgFn}8~sMl9n{Ep|k+Kw2+pIF67v#v1Zh zIu+f6LgK6iBi1puGYl1@m`2awemZ`OzXE_UVE+QSQv3UhxDHF%#e)S#a!Xlk&`DM& z0$c#(c4Kqsje941D2)*GqvHv|F!Pm@s^^w=G^f`kgGNpO@OccwVA^F5()yTiwq5D& z%|;stz#sInM;uCPfT(*u{(kA;*l{iJ8n3qjfzbQM>=IQfhK|R|j_;ge^_HNKgt7*~|6C830?Dip)vzy{v*pdS+y> z(Y?djx3ygLY(mi!`{jdBi>dRota6o~Bx4%Q<-`96Om$oSUm?rxYZoHx=i>uhI~gZ) z^?zs^!*K$qcfqSbL#FmqdG`qUk>aP6x9W=6-WqJ5aZzxgq5pjo1ac)M*3t?-cdXld z+4NMw6PpKQ`rFq31F#H#!S?`Mt%1PRpy{zB**g3wp~rz%Bq6^<5{qo01WuQ)lQ@<+ znmbT^B4M{Rpm!T`X_`IYA$JDO(hy%gv_-(t7AKo?ONx!G%eqz@-58=TAD%Rp=S6@I z&2lq(60C6vrk$wPU|}dM?5XN$(C77~$elup@G8v(d5$u&jSnNC(jMjX>K( zjpw}yj|&12P%Hqe<{qS7I+qzDcTT$wx=xnIIk%bu`RBT#Ww|VRTLg+E1fv>?vJ%*> zex23orb;-HklaWlWSLDZ&*R|Wpi;2Iin=kFrWI7JosiQiqae7D;;Qitc`tO388Sf# zBeQzJ%Zm$U-h_!Rtf9IepQ+HRD4umTQkC2N8b?6A%>tAJ-6Jdu+7OgL2W<#%BQVR+ z+{Oepvu%1HhmC`D9GC4(of#N-x2o8IhXeB^H{vk|e9Clu^nTYJtA81EOjMUc+YI6U zVzeVthy{Zpv!Pl9q*D->#7uD6kO6rC5XgY2kp?t~-mN$fnm2%>9Ubq%{|lOH1g}C6 ze8^^Y0i>*ujuttDq*skzB{)B)Rm^j2;)6ylBF!aXLiA<5lSwgwPhO@C>DFaw3-V$rA;*p$5G%o-9_wW9^<@w~0 zm(YOxS9zG?*j-r3V{K35R#+n4lK|lJ zV=s>RlxI8Zi|*(r<+R_75IG~fXF7TM43WolM))JCRG%E0N&Fg=T5Oc zpz$D07_tXSPEQX1`$gIy$>l-NjP=F8lvysucZ?=6gIB(hW?pl2DuX9J5)btuBR$6X zclUkDb3x=Z*egjNUvuEG)>^`zpi5+VH&4|v2gW90T47prmT@WY(=X;A{unw~Lv*lF zusQc;_CA^1%}4MEvIWi0E=e9n>LObDiRdDiLJ74_voFzz9Mm*{zP#q|_45gbE5qNze{j&rc9fY^c{x(V}H0cmtcs3P^gP-w9Jd6Dd zJeF%q6iYrCk;E#i{WxP-uS%(JZ~ay=%Gd#{mGq zSD}7_vcY#t8LS9JK9W3*pXIMfzwC~)n)3PP%lu7|@AW-B=Iw-*Jou;={zC4i90nl% z>A@T?Z9dQtMkEKPC6Ht^ntRa8eo(rmqxXySvR%e3^jZ0GP00AOCFLDSa~ZY#Eqm50 zMtM*h0ld4M%54MDdSs_}E~1?PGG4w9M&iHstkqcw=pO7px$)|2=$kL^&E>}RcT9WM zy~N8$#i#-cO%@*?XqA4{o1t8~T+k}0RgCOG_gSi_S9c|uqGlpWxv$#0ct4S% zhQoqhr}=jMu-prASQXv4AX&!7NbDRa1)v%0o4^jsc?SSh@h;l2sS|h{jzuNy_xyiN zeRn*T@87>lZk@klPFSh{TywQ9|)`3~(p@4iDA z+pJp-+xAZ`W=r0L32tp0Wg42177Mc7dXApf%#b?kR!yOYjgFxH&@00wt0p_lb!K)_ zHj5Ze{_d3gnfP^hh3AeeOT&uDs>U4K)z}u(M@D`PC9pJDNE)MLjY@Fh#}FZJ>Q|&= zzOL(-ZYc9qC1iQE$&V)4JU3-C9x_<~- zhFODs&*Tv)1;LFSMW9=S#0S?JtPzfJnqSQzIk)b8X{yK4;F^nPbIpZBT1Q;f3M4{7 zo)Xq^ZppF3xa3F3clMNm>D@6i&D}W{{1opHHg%+B&g*zYBrr9d1~BnIha6@~CH!vw zD0`-_FdJ#~FK&4y(C}ntW-4`m=!%rM@@dyPxoL^(mak+jXCw4F0Uz+>AE8RN$;qp5#LJ|rPV@J&yte^0bXPCmDX{us;kuB>)$C6 zB^RW)JM{hF#Dpz7?n?hYI0f^v2Bs$6q!;=0=)HGZyy%OOLmYMYvXc`6|B5d*F_+MR5et+Kfkm>oAidR9BjgCWYsdiOsuG@`qx-_%hW#r&E_$Kr?L*Tu(zk}`% z#=x$kl!CTvgr!>d=?pSw;T^gIcBe6t&>eBtA)@%ykm^rAY@HGeYV$N96*>imk@5j3= z)a;VyJY8V_UbEpNP@?fjP4B3iS{L#13Eq=t{G-UY)~}?ra~L>k6Kk^Ql?Mw0&wix3 zNOJ3ANdEBB=DXX%(lqDZCnV}kO8G^(?7a6{$njR=P;69zeVPVnOPDBr>A68iNIPKo ze2yFSr$ciTEqoX!qyjXjdAf)jx}%2aQwW}r?Yn@W&}G<@RZAoynAeWdcDWdrKY+ACb!O=XJM7DUG?Y3mS@FY^ehYx zFWq~o@EHV!@F;KW)Ui4~Jc2t~XCerDQHgerd1hAscdz%AMPhefTIXy3@GrrCnzQ}p zX(Ad$k-Y|`tJ?B|Uwe%*jJ!VJq4BT^;CQ>&@pDU;7QGKJ5nWJmFNv%s^tmhhIj=)Z zyKmgKu57np&@m=cM-#*FYw|Eu&ass#BL0TxqBiQD4Bxb71#gHVp%3!b?J7SosM^S> z#i#8cAmcdXap1*$SsmS=e}fB7_u1hnx%w>(XZ6%ij8XPTI(}BnJdnu8!TrgHao+Qi z>-MUc$h(mqUV)Ogm0~8h%USs=8?KMl;LE6VXx!Vi-qXIq>v@>k_}S;j?Q+Ihxr0vV z>?e185GV`diIYZO$)xp7MRa+H0^@V>@3%w;Ze2R}iiTiqmVmBy2_X38Ud0Sty^i_W zMR#-E%2$iHdMdi?T|cIMa))g+^K1*PpDysv* zuYb|+FVES)Fv=O6%g&F7I!0;i`CI2Z`oi_qOF; z-^3G(x2zN0ag%L}eWFRZid#TK=aSq`tJR?5fIMloo=Uo_7tZe>YxK4@k9<99vfsT+ z8%ytf@m5RviZg%h7I)YHf-(I5o0bW3A4gZ{N%^jz1sh4r*bkp4nMz&g&u&%n-MFNK zbECYc_r%s;^M(9VUBiZrX*Y$2k3K88k)>s9=L#)wzjehUd=LLV*@kS0)W99T5nfPP z^@h4tPXNkp=~?b%k+00OVNAzby~(B2h4~3kuDH05SX|niLOtOrFgib}E?%os6rk=B z9SeVjn;#BKWQHr-2}!dG8I~mL8qnXF4N8l{gb;13{7qHqigD+nq*~d$M12*VB)hu0 zCZC@jxpy+%E~Am#ZzO%+$K4;(^0}4y-zS8bWhYm`NLBXP7t{C}JSzY|2z(GwcR>K? zU_+Cd_A$)^tuh_4C6tlBT#Gj^glYS6YanqrT2rM(HRk3xQ#R`z@RVmQV_9{}=u7B? z7^WK3oG}#*R(`pAXqU!Kwx=!4k|KN!EsH;DFtPs^wi%+=k9J3bVm zOqO5>9lkDZLH%fU!=w!~YU>wzLi`tKU7ph1Y`1)O#~qQE1qIv1Ndu{C3e{z+dTh`;etMpU;+-f^kOI6;n*zYjLhq+N<4QDHMaB?gH`Y^(=fnXq3VatS#8d}cO>+^jmWayB?6fqf-X>lW2D zn8=I1e{_1<)_)OnVw|Kd%;~uylVO?a#fvytXI^YcFp4l(L=dG1vb{9l7~4SL<}i~% zzT_1u!DMdhYl^2-$CM>zv2Iv%fYEs{;YQlOZkuo;G;(ZrLCUjpY;5d;y81HPDs#cA zbfDsZ4zHP+vBUn7pm|U^^+VZ1UUcEcy*t$I?a1L%$*})w-42XEE5PKLD5f>>A#-s? ze(xQ3e7x;YKQz*JP$2AfX~`G4tS73A6eC;sB)x%~liO64-oBfEO|c0A^~76%VS5N6 z4ao1>9SS{egWaysQycFb!$L$Eg#`dO8cVwkL>mHxBwl)VAE01(rSitLDj)Ja~B-kv(C(tn6hw<|7tcdy=p6gt{2v<-wsB&eD0{ojB8$#QQFs*ofF zFVN-eq4#SeBy2j|{?j1$;N~gp9m#1P-SH}fk<2MGxs_x8E)-T-cjFrWk@A*-38;_h z4IQCJ+4a_koQX#`!p1c#P(ZlIfo#)m{RkZ5eJ+zj(L!3}RmYLqiq`vrcr}@!Hotlx zIi9-xWY=Tr{_$V`7P0((fX*u!xtqTlk#DFd@{=PRc^%@^(k*v>-T(ALvMZa#G@UE` zv}B(yhr$0xC_m73a3~aY^K-0uV_J6ZWlfBaA?2vowq{+aW*u@1Xg?q50M>O5W9@5X z*l>aKuwDHF%UU02qs5g+u)n?igPy4Nv zd%Yn3KOb{yc|yx!LFTeF3uTd>a$2Q89t+-mMeK$Gcgn6R$2kt%YtSgL-;}1h zJf?xN=ZN6{;(okHjf0fQMOS1$qGoF9jsX_&oIey)TlW87hxgD8{xB?EBv_kv?Za1F z!Ro!#gS)@<|NKf4N~gYGTx--VH7XuD?YA-av)=p7aNGa#2GL6R5C&i8YO?<%+=xFf zxGCU-;b2RF`~PCIOdi)#djimXq4-#BD{3l^A;i+KlYq zFFnRDbppYCmnZP<-gusS)-)Noh?6VcRVJNI}3Zu%Yx?IvB$; zcWoqp1#drT{B`5)6^WL=KhPS}#f4@YAnF?|f2+2$eaz=ZM3#Xl61Z>4e-?^bJTz!8 z_M++%K~8|G{StA$b9HTZTcdcIb;G~?Xz^3G&^X>$X#_2gcAYb_|Mq=DQ2Ab4x&M3f zTM*M~qzV4%HI6?{0P-(5^Zyk+s|WIFHE141%x!BK%G2kfSWgOg^;r^VcrC``_JwX#$D5r#}}k&ABI| zvHJz?9DS|A2M_rc!z47;HG`LF00h$d_Og|BPxRXq?sjC>dAH8*JCfgdzt5%Qye1`Jw23C``qw*95(hom7rpEw{dtUM+YHjQ zgF`P6^x*?H24Fjww}JdO%(9IsL~W&ch1zd0`_o}1j-#+r`7yXCU7sGPbPa9u`y6}{ zo4dX9U=k_huV7Hqn0e2_%>0GFt@4R1dTL2PPPYv1cm7>7JUrgY!X>B=g4sj68cR;x zuQV3ecqi9e2mroGz&d{g+cynAyv3WRxG z9~iaQ^AB^0a@G!Q%kD8(QAg>OPQ{X8+x|Z#VES3Ii~m)5c}1YWs+$-bmMlI0k%e>r z8fj+FbFbs~0Pt^0(Xtm<{^*^f2icK&VgxDAf<;uXE3H9_yj7aTDT>(;kL>-S(yzNN ze!oVDSM0%<4F84A6Rm*ys}g2(R*<#wU{}Un)uYoJ&kJ-I@5j1WURzAxA^sA4ZY&K< z%!<=!uI6>_@P;YarIMy`!Jm)U)ywdFb6)6&3i%_G@89gQ{IN1{ejcCcOCd6G>Z~-w zd_;ZCcis^_uC)5t3-vdgejW9@WxRI3nyml+=n~6LtoU^73O84flc6pJuZ%uDvJxto zBM5np;*9JFO+dwXGiP4s2@b3b&K{ow|QZwaZ0K0r;&fT^kSPV53U)a!f?&8 zHN6LKt)QR(Ys?!nxyt9B_9k5v#(n<^gFZ%~(bVSqcWMVqdPhB%pyJFqz{Kt^xb~8Q zO(*MJ(Nf_liQxp1Vu#7SYqn(F>b=%|7+*e$^#37Co*PMCPSYq=9|D2ST880sUT$U0 zLM}Dia@0lm@16e?3yqhci{AUFvg2jlt<$EnCrb+)nc9P%_(oQpZA^`m%s%Ic$;KiF zWZ%=bf;^w#`LkvPn9qR~#WtLRlxxu;8S-vc2lSm}9=SxHJDnf4CScw2z=!@X)$Xl#Tt?lsGkq-jV-63#yzuN{^}fkJ zRsQaMB|p`YE3#KssWtd07Q$;N)ywV4$nt2fm7S7Lm)`0azjo?kMqA^W6x@mNqTM1Q z)LJt>a#v!aHI8VUG8tOW^=TVo;iuyH?`!9VoZEMAtTp{OQ;*3dugY$l{gTS(x$k~C zM=n(UgTkw#c9XYZw^WtTvZ##$#hxx zE|chw+a{>D`$s>&wYh^^kDn0Z$|d=qThj%R>a5&jar1g*+WWEpUa3Z}rWAg0acF**P1KXf%x4C28E*mp2b(3#==FB&WaB9l za)(x{T?p3A2)h{W?seqidXMdlD;iL&lP-ga{pl0^y%MkE?=&_DR=tO}(Y0&nwGe?? zFjZ|e5-&d&AZl1{jj4}KyU*ryyIwWq*`5)PEVJRv(@n`iHo%pIbiusX4G|7mpC@N~ zkCt<*{PupnOYCK=#ue(}y=;HGsszjv-k&xp=2l(*jl3*xr5nW~ zWAsVFk=UXYn=ci;sg7lV{P363TCCCBctreSaful06oM|m}Xh0CHv z9eFP|o3mtm?LH!<)O4)7!}!J{7u_gYA+j&{=Zwy+jM3}Ga3LfveF#C?L&!EvK@}6z zGVpQW)tes)2T~3(&ez69K(MH!?J&&wFn@WSy@0>r)61=q!Br2k+XR(k{UfU{zhZb>b-Jm7U*Adg;r**Vi=TPJ-ypgKqne8f3d^v`5JNkV8N$$J z(pB_K*LMOkAab>g2}+y)ikDUK5Pu4XgBfH`8NBZn#@KGG)!_=j)?ns#V}yTEX8Y z`_$9GVi_w~#-yJ3YvStc4kBzM`bU=#Az;{)Da@BB+9^jgeT3A;6`E{-Jl|;eTv&dC9xS9Oi99CkFZ4 z270c9Ct2o`vGdUvF(=30jag+{eEs(AC|*6&V)Q}GtT#TLokZTpuIrH11pw*1QL_FZ zX4q1;E_>O2lGFlBX`k5txQeka+VOivnva991m$e*Bi9 zSB?kiSYPn`*$w?+g2wW%{ctJ^avyh$S92=rn8hqIJ-&EETtgeM`59&_0P*1ay+lL&>Ft!2wnNo zmh{B>E>DbOho?@}?=HS=vnjuFSlEZZgf=1Ywj(pl*I|RgH-?eW7 zgz&`>HAq629-Ac>+vyO;K}-%hhe1FV+d?*r^@hlU&2HLxP4BU9=O|N*<4VhdTandQ z{csNBuojsv7xso$57GtLHY$*{8z?vZ+fv#AbMyT%Q|mxpyo{+0VIB~DAf5~2Vl(Tu zpc}Ji!zydciLYZD89{t+y3?q!IYsTD8b6A_*B7ou?Rgm&(GR*nj&O3X0d{D$5RX!} zCyJ|X^Zn6{@w%!QA~CT}Fi2rnDCD5z@t-?9NcGwKpb~#T=At}PA5M%>0Wy+^ zLkXZKSmZ3D&a_TIaN0ZcPHy5|?J&4Q7!?;V?nYpiV*J-D`yzy+;u90sRoI}jr(~kD z|Mva6-EU6VkQ7p>U7vTv)Iww`H>3q-b5*)rYOX~$LqX<%_Sv&bKL4^9q;`$Wp^{Fs z$Q^40oFiLbcXlz+`t^k${o+;WP^Xrcmy26j!88|K180|H)VVKTzHC{)YUO$kaETFB z1n#D(<&)a(?Hg9vnwBhw{UW4TX=yeoL04;|bxlI%-=|xgYT}%-y*;uz-sXdd)v~P% z18Rw9%+<(+fP(bzyv{1^unvs!{X@2&fWJ?g#mq8yCb#*iZhRk?4wjhT-|Vcg+Yslx zr;=?`Ju2=W#TqWXsW8M~3M#^}o7m2Xo^@uh&^=~_2B;F?FC7o$Jej7^Y#987wUCMc ziOXfTHv#s*7r3yyeb|w(RA9K?@4Hc`7+#A86@gqNHe)PPLl8kflmsqzZ7CnHth9Q0 zph|px{GP$&P&@h3Z3HlW*jDaj*=vmk^qw!)ivuG`20`=K5eQOu7`*4|b0_;r5l{cu%FaIBNDPYQDom$G#Q(x+Y@yLbvOu)?V!L}tPgnV6nIg)Ft`=a zA4yT6Bbn+%bi9sz-;0t4UF^U}mfNEuH?@N6K3UOY5k;~Kc^y^ujW@ct2pZ$@v11v1e!zn~FjNiUY7Lwjz z&)0ZBhL0FsYR2omSF=14x6taXh4dE7l8_PQTqLNId$OyB6!0~5|6nqwly{G22oL^vx2H2yUAGsAtHF*4baUzn!r>G{htt|AN;2Ht zFtJ_#IqyP7e(;N=n_5!$wW3qP8r=2J^^4`@w0W*2)}Z86m|)XAeB&SmJ_+R=Wp_w1 zh=CbgFj}G$#-%tY&dSa+H@Zis*kl~{R&YBDf=w1rBmp15xt&T`m=*!-3Rt!=vt{50 zmFrEOy<5`JmEIF>^5@hkJXObI8~ojP)3H1z|L`Lfq)>=Dc-Jf?KaAWaHp3t5l(c>w6gzNWC@skK z?(6gMID}s@=$SxYAcN&XGzL3DPkq7pZ|VP?S)zanBgitz?k8UdI7JPWBWsUU&1o;i zU@#}oc4)4Z8gW5Sul8zuR5@_}h8)}LR(#B>-S8}qLKw^5VCUeh+%GHN?^?J=-T|9i zn|k5&rj)#qHK@jby=TpsRM_dP@i`Kovf^!lFvm5KW%q1!mO`y*YN|ihMSa@ta}`%? zX;rUBzD5VF@2QLY+eEkavHP|$M;YQdZYTNm>0vJ|db2E-i)w1jK^0CtapC0AB}hMb zfM@(t90v0z?kn${`pGX}3CqrRU~S;;SLvwAzTTcIXkUs?>SXz@i#5He&vJ>ks4r?G z8lP<}#oca7%N?|^spf7uWJdK}NF5m}VLY#^dj_=NaN`|z4)pak9!i+mz%O2@>evzn zsozg^_tXC&*-!^NW7I-FA^DrDsKxYcyMhdFb5Ujq z>kcyPzjs_SCU&Np_LL)k96cZgAnq7Vz*JW~8_G|NcqfBoxeIX~7r^arFS5Mq2VFm( z@t3^{X&m8x!l)M6Jd`{z(Gf+Bjl_P-xz<)m6_6Hjh~P0p3>S=uF(gT<+Cv>A4z4gJ z)9iK6T9;dh&1jr$5{qD=ZGL9Z1X*RMs&QP$Onow$BxZZ|K_uj5607T^cx1n|NfRci z#Kam}>l3S1dU35u(~72&=Px}yT}P@0?}#dyP<3oN%lqy7RNO{bpn-HpAE%wtRChSr zxS9jlr`b?lYhD6+15wP{>ldQs-Q_ULlaDxXm01s=MpH2hQ#@D~4VP}L$9;h=ieXUW zT^0_ZVmx9O!S=)9pej%bqW3$T;k#h?pP?QF^%P66wul84^H&350(W5-rB z=s4))HcaA*RlCnMxc86bV-csBe&I#)Qi5$TNL-;x1;yoE_Wiyq*0S1OyZV!G?})XA zxW$ieu>(0_XI?|C|GZ`9t!;lk6?8L))yEu&U)M^nBo7T4ZmUr@?8l~a3qo(wP>8~4 zgj6hqB1pW2(81cLcz#T7e0!BeDNL}b%bB>n=`Boe&T6sWLOfq6G6OJwYa3^k7B5CtM1i)=Y4`0c4&eNpKqF03MviG& zxZ+YkCIjGr&2YLjMmxl{#x5<7+tbMCTkVtD$Fixd!3YIF(F5ts&UZBG-oG^2E2iF zD00-*PXIeZuLi>;&WfIp4Hb4R^);!ap|)tP$n38-3U;0`NsJp!#7Mn51x;Ligy|0t z)hqnvw=7s*z1sTDK&Uq*pFKYWTwl zvYP2=HK=w)Ku(2e^W>06fNh7C+wB|7fkX)9DzNfL>-6QEZ(RcpNP!NQGG9#|Y2g)+W9S z&hAe$_(}C0A!@iXK`rUeCTZo&Jn^-xf{mSXM8iZ1F!B%ClumPF4Fta-dMpUVQo^RR zWV_f313OI*9tnxcpO}CM z(DU2lVEh7&b)_GHH$0wN)rds!{}s^t=BQ!2Yh!TW~!Q>F6VPDlDt^ z&;$g}aql`7L#7jzPbmyiiV2D4{I0Y_-Ta(t3O$-MR4tCUCR#lFMKC9J$hAUOh{J`% zPv*9WgC!t`at%n)w>>!ya8w{k%kbWG?$bB3Ay&!cA1(_)QmkS4PqL?Ujx^2DBr=!r zIu|Zp6h+_LGVnEhhqYDCQVYs2`29>4^(Jeid6NAMLIWVk{X^<%br*E1f27RbYIkTk z+7%kZyHJz+aGzu%!@wWp!2|wD5h(f-kK`fTL~5t2F9X+)*a%&c5Q+-NV*T{(GK1cA ziJ2tD5AMXtb_3^>1itKBH9v=@tzOI7Su0rwdN$+F5NSnnNm}&hbW1Q4LVY;-Encn+ z#brj71!1s~qg*3Z|8UBxRGcO#r7yu)9MT8fWA<8k-!wK5O-wkFp^ z@@xjZB`T>?f%HS`d2UY>ol;J7n3V>hSRTlQR4z}C6D zN_L{TgwVV|;BYH+4{zg*Iz!ZzzifcOU5!fG6jVXUQ&HMrgZr21YAqr7m!IFqM2rqI$>4E0J$qr?63f>^!!s4wO)q4x5iu z*gzrz4s8E-x(UcU3@G9#Y`l-e>ex@4ec7j1**i8+$F*_Qa#&pIqoE`CZ7!EXDboq- za(0uUIwg^ecPz0$WOBE{m#E94J_`gNam(iHhIZvDvAUiEn=e@}0rq1)Dmu0jG&emx z6(AHLiiksiv-H0l-OSd(zh6~o4K7j-T_U^58M5D`ra49Dq)>wt47Ywn6=q!T`j{`L z_xeHy!X)EkW80SvQtFx-)DK?S(1g@O13v}8q5tVUI`ykMOR_Ikdi6cI+s%I*9uL0Y zDx53G6*aIo$GIgc9R(}V@_^<^lwt4ikp9S{ zW)+MHf$nv}(6Cdu<$zFe8G2VremSGt&@wuJCibrpoetOLnA#htF815^HHrthk5(Tf zwoDAysSg}wYQV6a9M39jiR~N*0t!MU0qKaG>svzu6tX_G-FOr{aZVEdBcCG#1WIw~ z+S4!W$_e(ZTT)B9AbRH|#1^f7U=9af^UZ~P$!;thIxUm*jIoQly7&T^*hzh~OXG}G zHR5}hMA3AeI%xVtYV(4Bi^B#?Nii+pA-5jZ1+>ztf8HQQamSB7+iNQDuY<87O)p2u zyhprra5r!o633*XWE~Nq+?53wC-v97mg-G>lZ_!5P)}^j7@rU%6(tcGO`6A%+hn`F znF}gJk3h!dsM+-Qet^crOxJbnI|Em)JeC=flCsgok&>A-8<>g^vf8z4S~1;WZ38%2 zuO2MA)?+}~tYvJ5)*fR!B?}9S-uqvW`RMJ%D3@K`NrHUu^C^u>IUXoag;wgRWS{w9 zLY!|{&0(erkhOT)R^L9td-xvg<~(W74?R|aQ&=HUR7VC^?d2V*MENf6Zu@66%CRjFFWGH@@0b7SRc+0;FtkRqG{LA2}PXt?t=@fq-$3N6yp9JLN}o@(C>jyq zq@7`|33@K(Qp7DlCHfPvPP~oq)soxTO+KZ$~V&eqwki1_Y9N6>)E|`u{!6-kSC+3 zd-~X|uCbdSKk^bJ94urZoHj5K^@Ra;*Smv4+cdyb#b^qVYELjZwLzt}_sWOh0f9o5 z(VO`)!=)@qRdua{*ahfFujp0_e}{X=r+|^vI26vb2saD?2ER+NMwDxLx>n&e(1gNC z5^X!QdQhV4B&Q@`3IQvkm5`3vgx4S|LR&8=Sx%*e@RkUp7ibV_objfoV^Qwokfx)v zKgd+EIUu#OCtPDDZqV?yaDf-&`;C<7+{NiX*R=AjjI5Rmdee2`q&*=R0}gyZ&A2-8 zo`RH2ZTiEETv87~qV*+@_FYcNbZxVN+vA@8VU}Sv_R(*}_WYi!jYWEv;PF`1Hgu_H zBfHq7shObav|KynTcMk#<{XS?bs?10wD z!p5|;C1*di=Es(3ZUAbtZt8BCN^#6Vvg2$(yM94<%paooc0hH?4aPu{=6A{rW-R2Q zKOEA(w7F`KUqkmyiD#B$cj3;IculH#b|9F4Ft$gLf6lZd+uNIZ-qh)-ns2y8KbS&S-0j z(XMuI2oma}ShWMi2-9P2pKI`WJw3+H(;OL>+^urF?usVmo;(9yGrHYS;%El6cwksm zVKmT-TtxmTTV{ggq8qoUe{w3${&h-RG8rq@Qoc-CVXG0uti)VGd65B;uA+f= z%D*Y0j8f=9<#S_M{WlIxao$^NvJ{mqpmVh8fEFq!V zG&|HG!GH=c58MLSBD6>u1Hh6*20LMkmqp2WWLazpswlx2<}?q{?@A^bYR;HY?Oe`o=wWu)Qk=>tnjnN z8JUXtow#(YmekO*F9KM&ma_GO6AzSkl24l4J1{&YM#j_VLdlVv@inn)8ihZ~3*!{2 zG_7mb%+1Ypen=R9Vyltr6(p%WzU$bJz^ujFj_ETd5v_cE$$)Cs@w z9;%ABN>4Wj9EE$tTJ$}waRqQaBEAur(AYaAIT&lQ;vV9_0UX=gkYWN&Vv@Ydup?(m zLvDqU7yduMkMMrQ^uJ%%Oa=WTPBK7suq_L5Elw|DyPlEZ?aTAY|AgDop~meWk~P-G zF`NDy>2p`=@(>ONhFocBA^U42ya<4b(Oh~-H;CdyWI%vM)D}#W6C!^jhbpB2Az)l!XDU!)l7B(`2 zG>u%l{+>qEqUMy=&RziSp%&i(;@huYGjY7i?8monZ>!9#Pz!v1Gnygy`yHHBU^9%l zM`Ttgu)1@4N{^bp9763&SaF;tDY{uv3w;h3H8q>KrSp%zSSy$+7;*ebHP6 z^31l;yI?*8I?EOS8~9I?m^2=XRV~7tZ@@pE>$B8LzSL{04YYDTAlu17t?tlw=AEoG zWu%!yL`CP98+^Pop1VwqVKU;UCDv-WhLAZ0GBKzF>571Y zBEMF)6wWfu?KhHia>IzVB$3ZRcTNm{$SH1nE#M2P)DJ=KvxcO%z@Gz;`#no%M+T7} z(k*URZuEJ{SyC{wiD7m+^ngX9@~(*TI%Zf2;}p&-*rcBU{f4=?l2p`7^aQAqBJilE zo$sC|Cn*_d;DD5J>BIDgHcP|up|`cHgb+f}-9S=2?#g2q;i_N}y_#Sim(U{@g~}e^ z6bxFV(DghlVAn4$G+CR`C+zrsHNQ0`gu{TFZpP?IV-q3-9&$WG0-4XDhzZp`RWQVM zJ&4=&zBH?+2C5QR=9m7CL zUmD&~^Cf*xbE`0y3mkmNQSXR&QPMuBse#v-lMEO`i_NQRv}1VE)WuA)$;XOCe&#}+ z*KPr^9mSb9a~6p)+9R%+%X;VRb=6EM;_A9id`N{{3*cswJE+R|4X%Hl?jZNz1P1t` zz?)j#W-OjMsqbH*j@#(0ui2J!;QROXltlf~K{sWv=PCixS7jcSq9EnZUzBNW@N&zY^p78(X zz6FF-hC7IM?WL;NWI|TOL1nYxyxxlUaNqOz_Lpz6Pp=THE+ezuknyoW3@O7m`IcOU zAME_>lngp!G!y8E{W>LxBlplXSZTNrg-w{YC6PyA^YAQwDn)K>PKooju*%J^TrF0r zoewFUGV#y`$1t+nfFQa4eaQ>!Y!&+X@>MFK~HsB_Hvhso7tu?lR^6xwAy`1ocnqk>XtAg z_=MoFqeESHf3eTRCXOq6SM*N~;Yx>_Z~6K#epi!X$6Ha(=>$ru*Aj3*|1qEj4-PF& zWECoD1Z4uJ9SDWh+2oI6KoAbUq{G#|ozbv#q19)w&Fe!_OJm*>Rl`MCuBm7}QH#9T z>}VC>e*X0O`HyRk%eWjYW(j(E^}FvcIwk3i1gl-OLrmx*e(z?WsQx;S!=XPD%rwTD ztb(6Ng@ecEVR4X3W4kYlCPZ$g9-EgxJ9rMP$_h4_v&#y8A|6x};%6}9$|f|7Rl>wR z^p9ss+*FEbH&r?8$HD|JtN8E@6~A+<4@R0r-=Qs;WoLD11i`=D1HpIQ6|FKtx=GV2m+|H&$bZLkHWno&dx6pn|SVgh3KHp1?KRqM( z_(xyR(-hGmtrd1@vvNxVmW!p-=(mV1-0QLdSvYa=)#w&=I{6Po+a#7(eY~oX?LDR! z>1`yvJdiDL*E@Jf;+3u}28Z+Jhl}cKFaP-Q>La=Ei!oDD5m{=?oK6Cbga?i_x#Y;= z=eHEvrgm&aPnPa*aMeWdW-s}j$43RTs zEDS7>*1O|uSDxZag|QFu06RK%mqXQcou;!N>s ze^qE2pcg@+EBV=qcISL66s51|= zk@qOUyz^g%C1UB~d><};g?YXd2L~+WI{t|JclbGqc>+`WCsfj=-%y{XCi&vd>Ys}H z_vvy%7C+n@U|cI^aa9xx#>5lk-Iqo1C9L9fTlMlq%W5(qnqIUMtjl6w7Iw_~^VSiu zjKutrosa7+cQ}5#%+_F0K;v#sAE42#>qH)%3pixNS9am{_n1PDzj=JigSfxs?T=mj zd~&B3FY%UgyWvF)n*7#u_e*9DF=lDb(v4!9$)x{9N}aGb`5J|6Ui{3l4^N$6nB55C z@0YI?`t-{l-e0_}FBm^+qu~AX8VeN`Su~#ayGvoW8QuLA-0h^x+16+$q{^K6$Jurk zc^$Ns+%>&@am@A-M_(o0{c-TuNX@~4qSY&h*0U#ek$2>Ey}Ni>3P~)ohT8GD-WwXD zWmbkGD`x!|kjFwk)uO+2$b5hPhAKUDL3w3~^UVhzq*UML+r-3c?Cbuip9O4u4?hOJc`-wiUMDHrm9o&QKVUMEGBxJ!|<$ literal 0 HcmV?d00001 From 2654f30eca802c963058028473c18043b4dea40c Mon Sep 17 00:00:00 2001 From: vergil Date: Mon, 3 Jul 2023 16:17:10 +0200 Subject: [PATCH 33/64] Adds complete package metadata Signed-off-by: vergilfromadyen Signed-off-by: vergil --- packages/app/package.json | 6 ++++++ packages/backend-openapi-utils/package.json | 6 ++++++ packages/cli-node/package.json | 6 ++++++ packages/integration-react/package.json | 6 ++++++ packages/techdocs-cli-embedded-app/package.json | 6 ++++++ plugins/adr/package.json | 6 ++++++ plugins/airbrake-backend/package.json | 6 ++++++ plugins/airbrake/package.json | 6 ++++++ plugins/allure/package.json | 6 ++++++ plugins/analytics-module-ga/package.json | 6 ++++++ plugins/analytics-module-ga4/package.json | 6 ++++++ plugins/apache-airflow/package.json | 6 ++++++ plugins/apollo-explorer/package.json | 6 ++++++ plugins/auth-node/package.json | 6 ++++++ plugins/azure-devops-backend/package.json | 6 ++++++ plugins/bazaar-backend/package.json | 6 ++++++ plugins/bazaar/package.json | 6 ++++++ plugins/bitbucket-cloud-common/package.json | 6 ++++++ plugins/bitrise/package.json | 6 ++++++ plugins/catalog-backend-module-unprocessed/package.json | 6 ++++++ plugins/catalog-graph/package.json | 6 ++++++ plugins/catalog-node/package.json | 6 ++++++ plugins/cicd-statistics-module-gitlab/package.json | 6 ++++++ plugins/code-climate/package.json | 6 ++++++ plugins/code-coverage-backend/package.json | 6 ++++++ plugins/code-coverage/package.json | 6 ++++++ plugins/codescene/package.json | 6 ++++++ plugins/config-schema/package.json | 6 ++++++ plugins/devtools-backend/package.json | 6 ++++++ plugins/devtools-common/package.json | 6 ++++++ plugins/dynatrace/package.json | 1 + plugins/entity-feedback-backend/package.json | 6 ++++++ plugins/entity-feedback-common/package.json | 6 ++++++ plugins/entity-feedback/package.json | 1 + plugins/entity-validation/package.json | 1 + plugins/events-backend-module-aws-sqs/package.json | 6 ++++++ plugins/events-backend-module-azure/package.json | 6 ++++++ plugins/events-backend-module-bitbucket-cloud/package.json | 6 ++++++ plugins/events-backend-module-gerrit/package.json | 6 ++++++ plugins/events-backend-module-github/package.json | 6 ++++++ plugins/events-backend-module-gitlab/package.json | 6 ++++++ plugins/events-backend-test-utils/package.json | 6 ++++++ plugins/events-backend/package.json | 6 ++++++ plugins/events-node/package.json | 6 ++++++ plugins/example-todo-list-backend/package.json | 6 ++++++ plugins/example-todo-list-common/package.json | 6 ++++++ plugins/example-todo-list/package.json | 6 ++++++ plugins/explore-backend/package.json | 6 ++++++ plugins/firehydrant/package.json | 6 ++++++ plugins/gcalendar/package.json | 6 ++++++ plugins/git-release-manager/package.json | 6 ++++++ plugins/github-deployments/package.json | 6 ++++++ plugins/github-issues/package.json | 6 ++++++ plugins/graphql-voyager/package.json | 6 ++++++ plugins/ilert/package.json | 6 ++++++ plugins/jenkins-backend/package.json | 6 ++++++ plugins/jenkins-common/package.json | 6 ++++++ plugins/kafka/package.json | 6 ++++++ plugins/linguist-backend/package.json | 6 ++++++ plugins/linguist-common/package.json | 6 ++++++ plugins/microsoft-calendar/package.json | 6 ++++++ plugins/newrelic-dashboard/package.json | 6 ++++++ plugins/nomad-backend/package.json | 6 ++++++ plugins/nomad/package.json | 6 ++++++ plugins/octopus-deploy/package.json | 6 ++++++ plugins/org-react/package.json | 1 + plugins/org/package.json | 1 + plugins/periskop-backend/package.json | 5 +++++ plugins/periskop/package.json | 5 +++++ plugins/permission-backend/package.json | 6 ++++++ plugins/playlist-backend/package.json | 6 ++++++ plugins/playlist-common/package.json | 6 ++++++ plugins/playlist/package.json | 1 + .../package.json | 6 ++++++ plugins/scaffolder-backend-module-cookiecutter/package.json | 6 ++++++ plugins/scaffolder-backend-module-rails/package.json | 6 ++++++ plugins/scaffolder-backend-module-sentry/package.json | 6 ++++++ plugins/scaffolder-backend-module-yeoman/package.json | 6 ++++++ plugins/scaffolder-node/package.json | 6 ++++++ plugins/search-backend-module-catalog/package.json | 6 ++++++ plugins/search-backend-module-elasticsearch/package.json | 6 ++++++ plugins/search-backend-module-explore/package.json | 6 ++++++ plugins/search-backend-module-pg/package.json | 6 ++++++ plugins/search-backend-module-techdocs/package.json | 6 ++++++ plugins/search-backend-node/package.json | 6 ++++++ plugins/search-backend/package.json | 6 ++++++ plugins/shortcuts/package.json | 6 ++++++ plugins/sonarqube-backend/package.json | 6 ++++++ plugins/stack-overflow/package.json | 6 ++++++ plugins/tech-insights/package.json | 1 + plugins/user-settings-backend/package.json | 1 + plugins/xcmetrics/package.json | 6 ++++++ 92 files changed, 510 insertions(+) diff --git a/packages/app/package.json b/packages/app/package.json index 3163995632..96cbf0c1c5 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -6,6 +6,12 @@ "role": "frontend" }, "bundled": true, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/app" + }, "dependencies": { "@backstage/app-defaults": "workspace:^", "@backstage/catalog-model": "workspace:^", diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json index 85a1677adb..9c83356c83 100644 --- a/packages/backend-openapi-utils/package.json +++ b/packages/backend-openapi-utils/package.json @@ -11,6 +11,12 @@ "module": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/backend-openapi-utils" + }, "backstage": { "role": "node-library" }, diff --git a/packages/cli-node/package.json b/packages/cli-node/package.json index c59a8930c0..b5ba1855a8 100644 --- a/packages/cli-node/package.json +++ b/packages/cli-node/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "node-library" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/cli-node" + }, "scripts": { "build": "backstage-cli package build", "lint": "backstage-cli package lint", diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index 616826b430..b39d4fcb0c 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "web-library" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/integration-react" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 3bbef65e68..1b6af7f9e9 100644 --- a/packages/techdocs-cli-embedded-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -6,6 +6,12 @@ "role": "frontend" }, "bundled": true, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/techdocs-cli-embedded-app" + }, "dependencies": { "@backstage/app-defaults": "workspace:^", "@backstage/catalog-model": "workspace:^", diff --git a/plugins/adr/package.json b/plugins/adr/package.json index 486ea800a2..b032ccad7b 100644 --- a/plugins/adr/package.json +++ b/plugins/adr/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/adr" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/airbrake-backend/package.json b/plugins/airbrake-backend/package.json index 06e5183a44..226126531b 100644 --- a/plugins/airbrake-backend/package.json +++ b/plugins/airbrake-backend/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "backend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/airbrake-backend" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json index 48a825f93b..8a156c775d 100644 --- a/plugins/airbrake/package.json +++ b/plugins/airbrake/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/airbrake" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", diff --git a/plugins/allure/package.json b/plugins/allure/package.json index f4809128ff..7a65d9f707 100644 --- a/plugins/allure/package.json +++ b/plugins/allure/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/allure" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", diff --git a/plugins/analytics-module-ga/package.json b/plugins/analytics-module-ga/package.json index 302ee1cd86..8fb8d42a9e 100644 --- a/plugins/analytics-module-ga/package.json +++ b/plugins/analytics-module-ga/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "frontend-plugin-module" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/analytics-module-ga" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", diff --git a/plugins/analytics-module-ga4/package.json b/plugins/analytics-module-ga4/package.json index 85f34468b1..09e7240142 100644 --- a/plugins/analytics-module-ga4/package.json +++ b/plugins/analytics-module-ga4/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "frontend-plugin-module" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/plugins/analytics-module-ga4" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", diff --git a/plugins/apache-airflow/package.json b/plugins/apache-airflow/package.json index b30abb3bc9..60afaa9a24 100644 --- a/plugins/apache-airflow/package.json +++ b/plugins/apache-airflow/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/apache-airflow" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", diff --git a/plugins/apollo-explorer/package.json b/plugins/apollo-explorer/package.json index 0b43fd63ba..b9eba3278d 100644 --- a/plugins/apollo-explorer/package.json +++ b/plugins/apollo-explorer/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/apollo-explorer" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 07b9adacb4..c5846d60b4 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "node-library" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/auth-node" + }, "scripts": { "build": "backstage-cli package build", "lint": "backstage-cli package lint", diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json index 0a34ab449b..706274df6d 100644 --- a/plugins/azure-devops-backend/package.json +++ b/plugins/azure-devops-backend/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "backend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/azure-devops-backend" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/bazaar-backend/package.json b/plugins/bazaar-backend/package.json index 6e960f384c..d41127a9fa 100644 --- a/plugins/bazaar-backend/package.json +++ b/plugins/bazaar-backend/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "backend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/bazaar-backend" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build --experimental-type-build", diff --git a/plugins/bazaar/package.json b/plugins/bazaar/package.json index 0536367512..3181e99833 100644 --- a/plugins/bazaar/package.json +++ b/plugins/bazaar/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/bazaar" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json index ffbe670094..38ba256c35 100644 --- a/plugins/bitbucket-cloud-common/package.json +++ b/plugins/bitbucket-cloud-common/package.json @@ -14,6 +14,12 @@ "backstage": { "role": "common-library" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/bitbucket-cloud-common" + }, "scripts": { "build": "backstage-cli package build", "lint": "backstage-cli package lint", diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index fe7f1f1591..5388bb3542 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/bitrise" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json index 9f3f2f0b11..e9ca4662ac 100644 --- a/plugins/catalog-backend-module-unprocessed/package.json +++ b/plugins/catalog-backend-module-unprocessed/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "backend-plugin-module" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/catalog-backend-module-unprocessed" + }, "scripts": { "build": "backstage-cli package build", "lint": "backstage-cli package lint", diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 73cdb47d07..2e2b3c2402 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/catalog-graph" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index 426f8e3297..8aef496418 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -26,6 +26,12 @@ "backstage": { "role": "node-library" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/catalog-node" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/cicd-statistics-module-gitlab/package.json b/plugins/cicd-statistics-module-gitlab/package.json index 89da579bf4..2f9064b580 100644 --- a/plugins/cicd-statistics-module-gitlab/package.json +++ b/plugins/cicd-statistics-module-gitlab/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "frontend-plugin-module" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/cicd-statistics-module-gitlab" + }, "keywords": [ "backstage", "cicd statistics", diff --git a/plugins/code-climate/package.json b/plugins/code-climate/package.json index 1c0576bbdc..935e56e355 100644 --- a/plugins/code-climate/package.json +++ b/plugins/code-climate/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/code-climate" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", diff --git a/plugins/code-coverage-backend/package.json b/plugins/code-coverage-backend/package.json index a4910c6093..9174122f38 100644 --- a/plugins/code-coverage-backend/package.json +++ b/plugins/code-coverage-backend/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "backend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/code-coverage-backend" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index 8fbda2ff48..936db59fb4 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/code-coverage" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", diff --git a/plugins/codescene/package.json b/plugins/codescene/package.json index 1bf5dedf87..aef649c213 100644 --- a/plugins/codescene/package.json +++ b/plugins/codescene/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/codescene" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index f8a83aa177..ea4f55ef3c 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/config-schema" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index 239f7f5328..158bc00a8d 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "backend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/devtools-backend" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/devtools-common/package.json b/plugins/devtools-common/package.json index e3424e1b68..2c8b382727 100644 --- a/plugins/devtools-common/package.json +++ b/plugins/devtools-common/package.json @@ -14,6 +14,12 @@ "backstage": { "role": "common-library" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/devtools-common" + }, "scripts": { "build": "backstage-cli package build", "lint": "backstage-cli package lint", diff --git a/plugins/dynatrace/package.json b/plugins/dynatrace/package.json index 6d4b10418c..852ce144b5 100644 --- a/plugins/dynatrace/package.json +++ b/plugins/dynatrace/package.json @@ -12,6 +12,7 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", "repository": { "type": "git", "url": "https://github.com/backstage/backstage", diff --git a/plugins/entity-feedback-backend/package.json b/plugins/entity-feedback-backend/package.json index 36c0f85be7..1d9aae90a8 100644 --- a/plugins/entity-feedback-backend/package.json +++ b/plugins/entity-feedback-backend/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "backend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/entity-feedback-backend" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/entity-feedback-common/package.json b/plugins/entity-feedback-common/package.json index 6abb002aa4..642988730e 100644 --- a/plugins/entity-feedback-common/package.json +++ b/plugins/entity-feedback-common/package.json @@ -14,6 +14,12 @@ "backstage": { "role": "common-library" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/entity-feedback-common" + }, "scripts": { "build": "backstage-cli package build", "lint": "backstage-cli package lint", diff --git a/plugins/entity-feedback/package.json b/plugins/entity-feedback/package.json index de9eff5420..1ba7a035b7 100644 --- a/plugins/entity-feedback/package.json +++ b/plugins/entity-feedback/package.json @@ -12,6 +12,7 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", "repository": { "type": "git", "url": "https://github.com/backstage/backstage", diff --git a/plugins/entity-validation/package.json b/plugins/entity-validation/package.json index 46455d3a4c..e9221c6695 100644 --- a/plugins/entity-validation/package.json +++ b/plugins/entity-validation/package.json @@ -12,6 +12,7 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", "repository": { "type": "git", "url": "https://github.com/backstage/backstage", diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index e509058b69..77edb71673 100644 --- a/plugins/events-backend-module-aws-sqs/package.json +++ b/plugins/events-backend-module-aws-sqs/package.json @@ -25,6 +25,12 @@ "backstage": { "role": "backend-plugin-module" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/events-backend-module-aws-sqs" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json index 59d397352b..573d3368c4 100644 --- a/plugins/events-backend-module-azure/package.json +++ b/plugins/events-backend-module-azure/package.json @@ -25,6 +25,12 @@ "backstage": { "role": "backend-plugin-module" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/events-backend-module-azure" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json index f867348a3c..e4c1508a34 100644 --- a/plugins/events-backend-module-bitbucket-cloud/package.json +++ b/plugins/events-backend-module-bitbucket-cloud/package.json @@ -25,6 +25,12 @@ "backstage": { "role": "backend-plugin-module" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/events-backend-module-bitbucket-cloud" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json index f7f9471dfd..55676bce46 100644 --- a/plugins/events-backend-module-gerrit/package.json +++ b/plugins/events-backend-module-gerrit/package.json @@ -25,6 +25,12 @@ "backstage": { "role": "backend-plugin-module" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/events-backend-module-gerrit" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index 5fd37cf134..71a4d33d42 100644 --- a/plugins/events-backend-module-github/package.json +++ b/plugins/events-backend-module-github/package.json @@ -25,6 +25,12 @@ "backstage": { "role": "backend-plugin-module" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/events-backend-module-github" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json index 1734ecf5b0..087e1efd55 100644 --- a/plugins/events-backend-module-gitlab/package.json +++ b/plugins/events-backend-module-gitlab/package.json @@ -25,6 +25,12 @@ "backstage": { "role": "backend-plugin-module" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/events-backend-module-gitlab" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/events-backend-test-utils/package.json b/plugins/events-backend-test-utils/package.json index 342bbf6483..bc54d17858 100644 --- a/plugins/events-backend-test-utils/package.json +++ b/plugins/events-backend-test-utils/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "node-library" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/events-backend-test-utils" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index 591c0c2381..cbf1dc8af5 100644 --- a/plugins/events-backend/package.json +++ b/plugins/events-backend/package.json @@ -25,6 +25,12 @@ "backstage": { "role": "backend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/events-backend" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json index 906d2681fc..4d2169ac20 100644 --- a/plugins/events-node/package.json +++ b/plugins/events-node/package.json @@ -26,6 +26,12 @@ "backstage": { "role": "node-library" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/events-node" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index 8b75149cae..9747356461 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -8,6 +8,12 @@ "backstage": { "role": "backend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/plugins/example-todo-list-backend" + }, "publishConfig": { "access": "public", "main": "dist/index.cjs.js", diff --git a/plugins/example-todo-list-common/package.json b/plugins/example-todo-list-common/package.json index 902ea03c9a..03d39bb49f 100644 --- a/plugins/example-todo-list-common/package.json +++ b/plugins/example-todo-list-common/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "common-library" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/plugins/example-todo-list-common" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index e1cc5178fa..da11b3cf24 100644 --- a/plugins/example-todo-list/package.json +++ b/plugins/example-todo-list/package.json @@ -8,6 +8,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/example-todo-list" + }, "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/plugins/explore-backend/package.json b/plugins/explore-backend/package.json index fedc9b2600..16174f7a51 100644 --- a/plugins/explore-backend/package.json +++ b/plugins/explore-backend/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "backend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/explore-backend" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/firehydrant/package.json b/plugins/firehydrant/package.json index 0bde3e81ed..960cf01fba 100644 --- a/plugins/firehydrant/package.json +++ b/plugins/firehydrant/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/firehydrant" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", diff --git a/plugins/gcalendar/package.json b/plugins/gcalendar/package.json index c44f6d30c6..8a2311065d 100644 --- a/plugins/gcalendar/package.json +++ b/plugins/gcalendar/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/gcalendar" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index a407737cde..d7214d86da 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/git-release-manager" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index 7147f0e53c..7fe0efd865 100644 --- a/plugins/github-deployments/package.json +++ b/plugins/github-deployments/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/github-deployments" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", diff --git a/plugins/github-issues/package.json b/plugins/github-issues/package.json index c975f3687f..89c5d19ba0 100644 --- a/plugins/github-issues/package.json +++ b/plugins/github-issues/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/github-issues" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/graphql-voyager/package.json b/plugins/graphql-voyager/package.json index 5bb3c253c0..5bcf02d73e 100644 --- a/plugins/graphql-voyager/package.json +++ b/plugins/graphql-voyager/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/graphql-voyager" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json index 39674cbb19..e283ffa24a 100644 --- a/plugins/ilert/package.json +++ b/plugins/ilert/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/ilert" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", diff --git a/plugins/jenkins-backend/package.json b/plugins/jenkins-backend/package.json index 7998932341..4ab3a59853 100644 --- a/plugins/jenkins-backend/package.json +++ b/plugins/jenkins-backend/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "backend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/jenkins-backend" + }, "configSchema": "config.d.ts", "scripts": { "start": "backstage-cli package start", diff --git a/plugins/jenkins-common/package.json b/plugins/jenkins-common/package.json index 4a3eff8e81..dd00527cee 100644 --- a/plugins/jenkins-common/package.json +++ b/plugins/jenkins-common/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "common-library" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/jenkins-common" + }, "scripts": { "build": "backstage-cli package build", "lint": "backstage-cli package lint", diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index 68fc955734..617290f5b8 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/kafka" + }, "configSchema": "config.d.ts", "scripts": { "build": "backstage-cli package build", diff --git a/plugins/linguist-backend/package.json b/plugins/linguist-backend/package.json index ae92da2492..5110021ab2 100644 --- a/plugins/linguist-backend/package.json +++ b/plugins/linguist-backend/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "backend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/linguist-backend" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/linguist-common/package.json b/plugins/linguist-common/package.json index c13f40dee2..f28a721021 100644 --- a/plugins/linguist-common/package.json +++ b/plugins/linguist-common/package.json @@ -14,6 +14,12 @@ "backstage": { "role": "common-library" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/linguist-common" + }, "scripts": { "build": "backstage-cli package build", "lint": "backstage-cli package lint", diff --git a/plugins/microsoft-calendar/package.json b/plugins/microsoft-calendar/package.json index 09bed7ec07..1cdb50898a 100644 --- a/plugins/microsoft-calendar/package.json +++ b/plugins/microsoft-calendar/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/microsoft-calendar" + }, "author": { "name": "Statusneo", "email": "reach@statusneo.com", diff --git a/plugins/newrelic-dashboard/package.json b/plugins/newrelic-dashboard/package.json index b134d94dbb..fd18e44217 100644 --- a/plugins/newrelic-dashboard/package.json +++ b/plugins/newrelic-dashboard/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/newrelic-dashboard" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", diff --git a/plugins/nomad-backend/package.json b/plugins/nomad-backend/package.json index 27ee1325f1..03d0b137ee 100644 --- a/plugins/nomad-backend/package.json +++ b/plugins/nomad-backend/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "backend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/nomad-backend" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/nomad/package.json b/plugins/nomad/package.json index c34db7374d..8db5ed7690 100644 --- a/plugins/nomad/package.json +++ b/plugins/nomad/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/nomad" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/octopus-deploy/package.json b/plugins/octopus-deploy/package.json index 8f8bf98a56..46ec2cff08 100644 --- a/plugins/octopus-deploy/package.json +++ b/plugins/octopus-deploy/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/octopus-deploy" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/org-react/package.json b/plugins/org-react/package.json index 220c237345..49765ba6d5 100644 --- a/plugins/org-react/package.json +++ b/plugins/org-react/package.json @@ -12,6 +12,7 @@ "backstage": { "role": "web-library" }, + "homepage": "https://backstage.io", "repository": { "type": "git", "url": "https://github.com/backstage/backstage", diff --git a/plugins/org/package.json b/plugins/org/package.json index 6f09955e7d..23af90d2b8 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -13,6 +13,7 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", "repository": { "type": "git", "url": "https://github.com/backstage/backstage", diff --git a/plugins/periskop-backend/package.json b/plugins/periskop-backend/package.json index 4567470c5a..f515fd4088 100644 --- a/plugins/periskop-backend/package.json +++ b/plugins/periskop-backend/package.json @@ -8,6 +8,11 @@ "backstage": { "role": "backend-plugin" }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/periskop-backend" + }, "publishConfig": { "access": "public", "main": "dist/index.cjs.js", diff --git a/plugins/periskop/package.json b/plugins/periskop/package.json index 1ed2e39707..2f9f23d592 100644 --- a/plugins/periskop/package.json +++ b/plugins/periskop/package.json @@ -8,6 +8,11 @@ "backstage": { "role": "frontend-plugin" }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/periskop" + }, "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index 8c77dbba97..964a7ed5ac 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -27,6 +27,12 @@ "backstage": { "role": "backend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/permission-backend" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/playlist-backend/package.json b/plugins/playlist-backend/package.json index 9ca2c3acfe..24686f93e6 100644 --- a/plugins/playlist-backend/package.json +++ b/plugins/playlist-backend/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "backend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/playlist-backend" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/playlist-common/package.json b/plugins/playlist-common/package.json index bea2af8660..4303113910 100644 --- a/plugins/playlist-common/package.json +++ b/plugins/playlist-common/package.json @@ -14,6 +14,12 @@ "backstage": { "role": "common-library" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/playlist-common" + }, "scripts": { "build": "backstage-cli package build", "lint": "backstage-cli package lint", diff --git a/plugins/playlist/package.json b/plugins/playlist/package.json index a4fb704fa2..33f1a94a3c 100644 --- a/plugins/playlist/package.json +++ b/plugins/playlist/package.json @@ -12,6 +12,7 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", "repository": { "type": "git", "url": "https://github.com/backstage/backstage", diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index 9d9efdb5e1..b8ce9ea8d9 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "backend-plugin-module" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/scaffolder-backend-module-confluence-to-markdown" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 22cb5b66a6..f95771822b 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "backend-plugin-module" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/scaffolder-backend-module-cookiecutter" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index d19c0a7f91..9bc7bbf06f 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "backend-plugin-module" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/scaffolder-backend-module-rails" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index db02c35513..0193427ec1 100644 --- a/plugins/scaffolder-backend-module-sentry/package.json +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "backend-plugin-module" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/scaffolder-backend-module-sentry" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index 4e812f801d..12e05629db 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "backend-plugin-module" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/scaffolder-backend-module-yeoman" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index 856bac50b2..59e80f1c1c 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -14,6 +14,12 @@ "backstage": { "role": "node-library" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/scaffolder-node" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build --experimental-type-build", diff --git a/plugins/search-backend-module-catalog/package.json b/plugins/search-backend-module-catalog/package.json index a552ba7600..c1f46b7a3a 100644 --- a/plugins/search-backend-module-catalog/package.json +++ b/plugins/search-backend-module-catalog/package.json @@ -26,6 +26,12 @@ "backstage": { "role": "backend-plugin-module" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/search-backend-module-catalog" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index d7831f70de..47abe5e3b2 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -26,6 +26,12 @@ "backstage": { "role": "backend-plugin-module" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/search-backend-module-elasticsearch" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/search-backend-module-explore/package.json b/plugins/search-backend-module-explore/package.json index abf7d1a49f..46c11a2e62 100644 --- a/plugins/search-backend-module-explore/package.json +++ b/plugins/search-backend-module-explore/package.json @@ -26,6 +26,12 @@ "backstage": { "role": "backend-plugin-module" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/search-backend-module-explore" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 5a1d5de17b..2f37f90681 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -26,6 +26,12 @@ "backstage": { "role": "backend-plugin-module" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/search-backend-module-pg" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/search-backend-module-techdocs/package.json b/plugins/search-backend-module-techdocs/package.json index 44115b5cf1..5a5e933523 100644 --- a/plugins/search-backend-module-techdocs/package.json +++ b/plugins/search-backend-module-techdocs/package.json @@ -26,6 +26,12 @@ "backstage": { "role": "backend-plugin-module" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/search-backend-module-techdocs" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index 445ba72f0d..b799d07d62 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -26,6 +26,12 @@ "backstage": { "role": "node-library" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/search-backend-node" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 9ad4f55f40..f2d4d0a74c 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -26,6 +26,12 @@ "backstage": { "role": "backend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/search-backend" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index 5572036823..1c1699c67c 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/shortcuts" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", diff --git a/plugins/sonarqube-backend/package.json b/plugins/sonarqube-backend/package.json index 79762943fd..41aec6ecc9 100644 --- a/plugins/sonarqube-backend/package.json +++ b/plugins/sonarqube-backend/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "backend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/sonarqube-backend" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/stack-overflow/package.json b/plugins/stack-overflow/package.json index 768e8f2b54..030255ec62 100644 --- a/plugins/stack-overflow/package.json +++ b/plugins/stack-overflow/package.json @@ -12,6 +12,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/stack-overflow" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", diff --git a/plugins/tech-insights/package.json b/plugins/tech-insights/package.json index 4721e0dd30..67df31cf47 100644 --- a/plugins/tech-insights/package.json +++ b/plugins/tech-insights/package.json @@ -12,6 +12,7 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", "repository": { "type": "git", "url": "https://github.com/backstage/backstage", diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index 8b93f7f723..caaa31faa8 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -14,6 +14,7 @@ "types": "dist/index.d.ts", "alphaTypes": "dist/index.alpha.d.ts" }, + "homepage": "https://backstage.io", "repository": { "type": "git", "url": "https://github.com/backstage/backstage", diff --git a/plugins/xcmetrics/package.json b/plugins/xcmetrics/package.json index 57ab0ad3f2..0a3ca50ca8 100644 --- a/plugins/xcmetrics/package.json +++ b/plugins/xcmetrics/package.json @@ -13,6 +13,12 @@ "backstage": { "role": "frontend-plugin" }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/xcmetrics" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", From 12a8c94eda8de58210e483db51fa03ae30357a58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 28 Jul 2023 15:24:00 +0200 Subject: [PATCH 34/64] add changeset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/hungry-shrimps-care.md | 91 +++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 .changeset/hungry-shrimps-care.md diff --git a/.changeset/hungry-shrimps-care.md b/.changeset/hungry-shrimps-care.md new file mode 100644 index 0000000000..665a7bfc82 --- /dev/null +++ b/.changeset/hungry-shrimps-care.md @@ -0,0 +1,91 @@ +--- +'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch +'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch +'@backstage/plugin-events-backend-module-bitbucket-cloud': patch +'@backstage/plugin-search-backend-module-elasticsearch': patch +'@backstage/plugin-catalog-backend-module-unprocessed': patch +'@backstage/plugin-scaffolder-backend-module-sentry': patch +'@backstage/plugin-scaffolder-backend-module-yeoman': patch +'@backstage/plugin-scaffolder-backend-module-rails': patch +'@backstage/plugin-search-backend-module-techdocs': patch +'@backstage/plugin-cicd-statistics-module-gitlab': patch +'@backstage/plugin-events-backend-module-aws-sqs': patch +'@backstage/plugin-search-backend-module-catalog': patch +'@backstage/plugin-search-backend-module-explore': patch +'@backstage/plugin-events-backend-module-gerrit': patch +'@backstage/plugin-events-backend-module-github': patch +'@backstage/plugin-events-backend-module-gitlab': patch +'@backstage/plugin-events-backend-module-azure': patch +'@backstage/plugin-events-backend-test-utils': patch +'@backstage/plugin-search-backend-module-pg': patch +'@backstage/plugin-entity-feedback-backend': patch +'@backstage/backend-openapi-utils': patch +'@backstage/plugin-bitbucket-cloud-common': patch +'@backstage/plugin-entity-feedback-common': patch +'@backstage/plugin-code-coverage-backend': patch +'@backstage/plugin-user-settings-backend': patch +'@backstage/plugin-analytics-module-ga4': patch +'@backstage/plugin-azure-devops-backend': patch +'@backstage/plugin-analytics-module-ga': patch +'@backstage/plugin-git-release-manager': patch +'@backstage/plugin-search-backend-node': patch +'@backstage/integration-react': patch +'@backstage/plugin-github-deployments': patch +'@backstage/plugin-microsoft-calendar': patch +'@backstage/plugin-newrelic-dashboard': patch +'@backstage/plugin-permission-backend': patch +'@backstage/plugin-entity-validation': patch +'@backstage/plugin-sonarqube-backend': patch +'@backstage/plugin-airbrake-backend': patch +'@backstage/plugin-devtools-backend': patch +'@backstage/plugin-linguist-backend': patch +'@backstage/plugin-periskop-backend': patch +'@backstage/plugin-playlist-backend': patch +'@backstage/plugin-apollo-explorer': patch +'@backstage/plugin-devtools-common': patch +'@backstage/plugin-entity-feedback': patch +'@backstage/plugin-explore-backend': patch +'@backstage/plugin-graphql-voyager': patch +'@backstage/plugin-jenkins-backend': patch +'@backstage/plugin-linguist-common': patch +'@backstage/plugin-playlist-common': patch +'@backstage/plugin-scaffolder-node': patch +'@backstage/plugin-apache-airflow': patch +'@backstage/plugin-bazaar-backend': patch +'@backstage/plugin-events-backend': patch +'@backstage/plugin-jenkins-common': patch +'@backstage/plugin-octopus-deploy': patch +'@backstage/plugin-search-backend': patch +'@backstage/plugin-stack-overflow': patch +'@backstage/plugin-catalog-graph': patch +'@backstage/plugin-code-coverage': patch +'@backstage/plugin-config-schema': patch +'@backstage/plugin-github-issues': patch +'@backstage/plugin-nomad-backend': patch +'@backstage/plugin-tech-insights': patch +'@backstage/plugin-catalog-node': patch +'@backstage/plugin-code-climate': patch +'@backstage/plugin-events-node': patch +'@backstage/plugin-firehydrant': patch +'@backstage/cli-node': patch +'@backstage/plugin-auth-node': patch +'@backstage/plugin-codescene': patch +'@backstage/plugin-dynatrace': patch +'@backstage/plugin-gcalendar': patch +'@backstage/plugin-org-react': patch +'@backstage/plugin-shortcuts': patch +'@backstage/plugin-xcmetrics': patch +'@backstage/plugin-airbrake': patch +'@backstage/plugin-periskop': patch +'@backstage/plugin-playlist': patch +'@backstage/plugin-bitrise': patch +'@backstage/plugin-allure': patch +'@backstage/plugin-bazaar': patch +'@backstage/plugin-ilert': patch +'@backstage/plugin-kafka': patch +'@backstage/plugin-nomad': patch +'@backstage/plugin-adr': patch +'@backstage/plugin-org': patch +--- + +Add package repository and homepage metadata From e66544d53a7d315e874bed051bb3b656daa29c5d Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Fri, 28 Jul 2023 15:37:01 +0200 Subject: [PATCH 35/64] Remove unneeded test Signed-off-by: Philipp Hugenroth --- .../src/CreateBackend.test.ts | 56 ------------------- 1 file changed, 56 deletions(-) diff --git a/packages/backend-defaults/src/CreateBackend.test.ts b/packages/backend-defaults/src/CreateBackend.test.ts index 88e6bddd97..28276dbae1 100644 --- a/packages/backend-defaults/src/CreateBackend.test.ts +++ b/packages/backend-defaults/src/CreateBackend.test.ts @@ -84,60 +84,4 @@ describe('createBackend', () => { }), ).toThrow('The core.pluginMetadata service cannot be overridden'); }); - - it('should prioritize services correctly', async () => { - const backend = createBackend({ - services: [ - createServiceFactory({ - service: coreServices.rootHttpRouter, - deps: {}, - async factory() { - return { - use() {}, - }; - }, - }), - mockServices.config.factory({ - data: { root: 'root-backend' }, - }), - createServiceFactory({ - service: fooServiceRef, - deps: {}, - async factory() { - return 'foo-backend'; - }, - }), - createServiceFactory({ - service: barServiceRef, - deps: {}, - async factory() { - return 'bar-backend'; - }, - }), - ], - }); - - expect.assertions(3); - backend.add( - createBackendPlugin({ - pluginId: 'test', - register(reg) { - reg.registerInit({ - deps: { - config: coreServices.config, - foo: fooServiceRef, - bar: barServiceRef, - }, - async init({ config, foo, bar }) { - expect(config.get('root')).toBe('root-backend'); - expect(foo).toBe('foo-backend'); - expect(bar).toBe('bar-backend'); - }, - }); - }, - })(), - ); - - await backend.start(); - }); }); From 041e02e2ba8d66b6900b6e7b75528f56c825c7b3 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Fri, 28 Jul 2023 15:40:52 +0200 Subject: [PATCH 36/64] Remove unused imports & vars Signed-off-by: Philipp Hugenroth --- packages/backend-defaults/src/CreateBackend.test.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/backend-defaults/src/CreateBackend.test.ts b/packages/backend-defaults/src/CreateBackend.test.ts index 28276dbae1..de04d4e350 100644 --- a/packages/backend-defaults/src/CreateBackend.test.ts +++ b/packages/backend-defaults/src/CreateBackend.test.ts @@ -16,15 +16,9 @@ import { coreServices, - createBackendPlugin, createServiceFactory, - createServiceRef, } from '@backstage/backend-plugin-api'; import { createBackend } from './CreateBackend'; -import { mockServices } from '@backstage/backend-test-utils'; - -const fooServiceRef = createServiceRef({ id: 'foo', scope: 'root' }); -const barServiceRef = createServiceRef({ id: 'bar', scope: 'root' }); describe('createBackend', () => { it('should not throw when overriding a default service implementation', () => { From 80f78e85b7653ab52990866cc779c37f437265fc Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Fri, 28 Jul 2023 15:42:54 +0200 Subject: [PATCH 37/64] Change changeset to indicate breaking change Signed-off-by: Philipp Hugenroth --- .changeset/rich-zoos-occur.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/rich-zoos-occur.md b/.changeset/rich-zoos-occur.md index 65d21710d1..979acb93c6 100644 --- a/.changeset/rich-zoos-occur.md +++ b/.changeset/rich-zoos-occur.md @@ -3,4 +3,4 @@ '@backstage/backend-defaults': minor --- -Removing shared environments concept from the new experimental backend system. +**BREAKING**: Removing shared environments concept from the new experimental backend system. From 2f185958599854e64f7591e0c1d3bb4471e1bb2e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 28 Jul 2023 15:53:27 +0200 Subject: [PATCH 38/64] config-loader: throw error rather than silently ignore invalid config Signed-off-by: Patrik Oldsberg --- .changeset/dirty-chefs-listen.md | 7 +++++ packages/config-loader/src/schema/collect.ts | 29 ++++++++++++++++---- 2 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 .changeset/dirty-chefs-listen.md diff --git a/.changeset/dirty-chefs-listen.md b/.changeset/dirty-chefs-listen.md new file mode 100644 index 0000000000..84b684b977 --- /dev/null +++ b/.changeset/dirty-chefs-listen.md @@ -0,0 +1,7 @@ +--- +'@backstage/config-loader': minor +--- + +Loading invalid TypeScript configuration schemas will now throw an error rather than silently being ignored. + +In particular this includes defining any additional types other than `Config` in the schema file, or use of unsupported types such as `Record` or `Partial`. diff --git a/packages/config-loader/src/schema/collect.ts b/packages/config-loader/src/schema/collect.ts index ac3e4af341..6c9737f7b7 100644 --- a/packages/config-loader/src/schema/collect.ts +++ b/packages/config-loader/src/schema/collect.ts @@ -15,6 +15,7 @@ */ import fs from 'fs-extra'; +import { EOL } from 'os'; import { resolve as resolvePath, relative as relativePath, @@ -163,7 +164,7 @@ async function compileTsSchemas(paths: string[]) { // Lazy loaded, because this brings up all of TypeScript and we don't // want that eagerly loaded in tests - const { getProgramFromFiles, generateSchema } = await import( + const { getProgramFromFiles, buildGenerator } = await import( 'typescript-json-schema' ); @@ -183,17 +184,35 @@ async function compileTsSchemas(paths: string[]) { const tsSchemas = paths.map(path => { let value; try { - value = generateSchema( + const generator = buildGenerator( program, - // All schemas should export a `Config` symbol - 'Config', // This enables the use of these tags in TSDoc comments { required: true, validationKeywords: ['visibility', 'deepVisibility', 'deprecated'], }, [path.split(sep).join('/')], // Unix paths are expected for all OSes here - ) as JsonObject | null; + ); + + const userSymbols = new Set(generator?.getUserSymbols()); + userSymbols.delete('Config'); + if (userSymbols.size !== 0) { + const names = Array.from(userSymbols).join("', '"); + throw new Error( + `Invalid configuration schema in ${path}, additional symbol definitions are not allowed, found '${names}'`, + ); + } + + // All schemas should export a `Config` symbol + value = generator?.getSchemaForSymbol('Config') as JsonObject | null; + + const reffedDefs = Object.keys(generator?.ReffedDefinitions ?? {}); + if (reffedDefs.length !== 0) { + const lines = reffedDefs.join(`${EOL} `); + throw new Error( + `Invalid configuration schema in ${path}, the following definitions are not supported:${EOL}${EOL} ${lines}`, + ); + } } catch (error) { assertError(error); if (error.message !== 'type Config not found') { From 7245b3e17e6de62177ecd17f7931f5dd950c5e07 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 28 Jul 2023 16:05:56 +0200 Subject: [PATCH 39/64] removed usage of Record and top-level types from configuration schemas Signed-off-by: Patrik Oldsberg --- .../catalog-backend-module-aws/config.d.ts | 9 ++- .../catalog-backend-module-azure/config.d.ts | 60 +++++++++---------- .../config.d.ts | 9 ++- .../config.d.ts | 9 ++- .../catalog-backend-module-gerrit/config.d.ts | 9 ++- .../catalog-backend-module-github/config.d.ts | 9 ++- .../catalog-backend-module-gitlab/config.d.ts | 9 ++- .../config.d.ts | 9 ++- .../config.d.ts | 9 ++- .../events-backend-module-aws-sqs/config.d.ts | 9 ++- 10 files changed, 66 insertions(+), 75 deletions(-) diff --git a/plugins/catalog-backend-module-aws/config.d.ts b/plugins/catalog-backend-module-aws/config.d.ts index b3610ccfc5..f3f80fc7db 100644 --- a/plugins/catalog-backend-module-aws/config.d.ts +++ b/plugins/catalog-backend-module-aws/config.d.ts @@ -59,9 +59,8 @@ export interface Config { */ schedule?: TaskScheduleDefinitionConfig; } - | Record< - string, - { + | { + [name: string]: { /** * (Required) AWS S3 Bucket Name */ @@ -81,8 +80,8 @@ export interface Config { * (Optional) TaskScheduleDefinition for the refresh. */ schedule?: TaskScheduleDefinitionConfig; - } - >; + }; + }; }; }; } diff --git a/plugins/catalog-backend-module-azure/config.d.ts b/plugins/catalog-backend-module-azure/config.d.ts index 940695c8ea..757ed2bfd3 100644 --- a/plugins/catalog-backend-module-azure/config.d.ts +++ b/plugins/catalog-backend-module-azure/config.d.ts @@ -16,35 +16,6 @@ import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; -interface AzureDevOpsConfig { - /** - * (Optional) The DevOps host; leave empty for `dev.azure.com`, otherwise set to your self-hosted instance host. - */ - host: string; - /** - * (Required) Your organization slug. - */ - organization: string; - /** - * (Required) Your project slug. - */ - project: string; - /** - * (Optional) The repository name. Wildcards are supported as show on the examples above. - * If not set, all repositories will be searched. - */ - repository?: string; - /** - * (Optional) Where to find catalog-info.yaml files. Wildcards are supported. - * If not set, defaults to /catalog-info.yaml. - */ - path?: string; - /** - * (Optional) TaskScheduleDefinition for the refresh. - */ - schedule?: TaskScheduleDefinitionConfig; -} - export interface Config { catalog?: { /** @@ -54,7 +25,36 @@ export interface Config { /** * AzureDevopsEntityProvider configuration */ - azureDevOps?: Record; + azureDevOps?: { + [name: string]: { + /** + * (Optional) The DevOps host; leave empty for `dev.azure.com`, otherwise set to your self-hosted instance host. + */ + host: string; + /** + * (Required) Your organization slug. + */ + organization: string; + /** + * (Required) Your project slug. + */ + project: string; + /** + * (Optional) The repository name. Wildcards are supported as show on the examples above. + * If not set, all repositories will be searched. + */ + repository?: string; + /** + * (Optional) Where to find catalog-info.yaml files. Wildcards are supported. + * If not set, defaults to /catalog-info.yaml. + */ + path?: string; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; + }; + }; }; }; } diff --git a/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts b/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts index 8fa58413c0..9e97597724 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts @@ -60,9 +60,8 @@ export interface Config { */ schedule?: TaskScheduleDefinitionConfig; } - | Record< - string, - { + | { + [name: string]: { /** * (Optional) Path to the catalog file. Default to "/catalog-info.yaml". * @visibility frontend @@ -93,8 +92,8 @@ export interface Config { * (Optional) TaskScheduleDefinition for the discovery. */ schedule?: TaskScheduleDefinitionConfig; - } - >; + }; + }; }; }; } diff --git a/plugins/catalog-backend-module-bitbucket-server/config.d.ts b/plugins/catalog-backend-module-bitbucket-server/config.d.ts index 67f694d419..0554d62df3 100644 --- a/plugins/catalog-backend-module-bitbucket-server/config.d.ts +++ b/plugins/catalog-backend-module-bitbucket-server/config.d.ts @@ -52,9 +52,8 @@ export interface Config { */ schedule?: TaskScheduleDefinitionConfig; } - | Record< - string, - { + | { + [name: string]: { /** * (Optional) Path to the catalog file. Default to "/catalog-info.yaml". * @visibility frontend @@ -80,8 +79,8 @@ export interface Config { * (Optional) TaskScheduleDefinition for the refresh. */ schedule?: TaskScheduleDefinitionConfig; - } - >; + }; + }; }; }; } diff --git a/plugins/catalog-backend-module-gerrit/config.d.ts b/plugins/catalog-backend-module-gerrit/config.d.ts index d8b48d2967..a246662b0a 100644 --- a/plugins/catalog-backend-module-gerrit/config.d.ts +++ b/plugins/catalog-backend-module-gerrit/config.d.ts @@ -25,9 +25,8 @@ export interface Config { * * Maps provider id with configuration. */ - gerrit?: Record< - string, - { + gerrit?: { + [name: string]: { /** * (Required) The host of the Gerrit integration to use. */ @@ -42,8 +41,8 @@ export interface Config { * The branch where the provider will try to find entities. Defaults to "master". */ branch?: string; - } - >; + }; + }; }; }; } diff --git a/plugins/catalog-backend-module-github/config.d.ts b/plugins/catalog-backend-module-github/config.d.ts index 5a699e745f..9755504ee2 100644 --- a/plugins/catalog-backend-module-github/config.d.ts +++ b/plugins/catalog-backend-module-github/config.d.ts @@ -118,9 +118,8 @@ export interface Config { */ schedule?: TaskScheduleDefinitionConfig; } - | Record< - string, - { + | { + [name: string]: { /** * (Optional) The hostname of your GitHub Enterprise instance. * Default: `github.com`. @@ -182,8 +181,8 @@ export interface Config { * (Optional) TaskScheduleDefinition for the refresh. */ schedule?: TaskScheduleDefinitionConfig; - } - >; + }; + }; }; }; } diff --git a/plugins/catalog-backend-module-gitlab/config.d.ts b/plugins/catalog-backend-module-gitlab/config.d.ts index f5afc71093..e9732c0642 100644 --- a/plugins/catalog-backend-module-gitlab/config.d.ts +++ b/plugins/catalog-backend-module-gitlab/config.d.ts @@ -22,9 +22,8 @@ export interface Config { /** * GitlabDiscoveryEntityProvider configuration */ - gitlab?: Record< - string, - { + gitlab?: { + [name: string]: { /** * (Required) Gitlab's host name. */ @@ -64,8 +63,8 @@ export interface Config { * (Optional) Skip forked repository */ skipForkedRepos?: boolean; - } - >; + }; + }; }; }; } diff --git a/plugins/catalog-backend-module-msgraph/config.d.ts b/plugins/catalog-backend-module-msgraph/config.d.ts index 904ce1f646..9d341215cd 100644 --- a/plugins/catalog-backend-module-msgraph/config.d.ts +++ b/plugins/catalog-backend-module-msgraph/config.d.ts @@ -209,9 +209,8 @@ export interface Config { */ schedule?: TaskScheduleDefinitionConfig; } - | Record< - string, - { + | { + [name: string]: { /** * The prefix of the target that this matches on, e.g. * "https://graph.microsoft.com/v1.0", with no trailing slash. @@ -296,8 +295,8 @@ export interface Config { * (Optional) TaskScheduleDefinition for the refresh. */ schedule?: TaskScheduleDefinitionConfig; - } - >; + }; + }; }; }; } diff --git a/plugins/catalog-backend-module-puppetdb/config.d.ts b/plugins/catalog-backend-module-puppetdb/config.d.ts index edd7ab3889..8aab239047 100644 --- a/plugins/catalog-backend-module-puppetdb/config.d.ts +++ b/plugins/catalog-backend-module-puppetdb/config.d.ts @@ -46,9 +46,8 @@ export interface Config { */ schedule?: TaskScheduleDefinition; } - | Record< - string, - { + | { + [name: string]: { /** * (Required) The base URL of PuppetDB API instance. */ @@ -61,8 +60,8 @@ export interface Config { * (Optional) Task schedule definition for the refresh. */ schedule?: TaskScheduleDefinition; - } - >; + }; + }; }; }; } diff --git a/plugins/events-backend-module-aws-sqs/config.d.ts b/plugins/events-backend-module-aws-sqs/config.d.ts index dfef35bf22..c2edfeae8e 100644 --- a/plugins/events-backend-module-aws-sqs/config.d.ts +++ b/plugins/events-backend-module-aws-sqs/config.d.ts @@ -31,9 +31,8 @@ export interface Config { * Contains a record per topic for which an AWS SQS queue * should be used as source of events. */ - topics: Record< - string, - { + topics: { + [name: string]: { /** * (Required) Queue-related configuration. */ @@ -69,8 +68,8 @@ export interface Config { * Default: 1 minute. */ waitTimeAfterEmptyReceive: HumanDuration; - } - >; + }; + }; }; }; }; From 8f7b618a490c3f8c067192336e4346c4572c1418 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 28 Jul 2023 16:06:18 +0200 Subject: [PATCH 40/64] removed usage of Partial in configuration schemas Signed-off-by: Patrik Oldsberg --- packages/backend-common/config.d.ts | 6 +++--- plugins/jenkins-backend/config.d.ts | 10 +++++----- plugins/proxy-backend/config.d.ts | 14 +++++++------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts index 3fd886874e..e5b193c543 100644 --- a/packages/backend-common/config.d.ts +++ b/packages/backend-common/config.d.ts @@ -77,17 +77,17 @@ export interface Config { */ connection: | string - | Partial<{ + | { /** * Password that belongs to the client User * @visibility secret */ - password: string; + password?: string; /** * Other connection settings */ [key: string]: unknown; - }>; + }; /** Database name prefix override */ prefix?: string; /** diff --git a/plugins/jenkins-backend/config.d.ts b/plugins/jenkins-backend/config.d.ts index 5b725dd801..1773ea7e75 100644 --- a/plugins/jenkins-backend/config.d.ts +++ b/plugins/jenkins-backend/config.d.ts @@ -42,13 +42,13 @@ export interface Config { username: string; /** @visibility secret */ apiKey: string; - extraRequestHeaders?: Partial<{ + extraRequestHeaders?: { /** @visibility secret */ - Authorization: string; + Authorization?: string; /** @visibility secret */ - authorization: string; - [key: string]: string; - }>; + authorization?: string; + [key: string]: string | undefined; + }; }[]; }; } diff --git a/plugins/proxy-backend/config.d.ts b/plugins/proxy-backend/config.d.ts index 410ff86a2e..bc9cf7765a 100644 --- a/plugins/proxy-backend/config.d.ts +++ b/plugins/proxy-backend/config.d.ts @@ -31,17 +31,17 @@ export interface Config { /** * Object with extra headers to be added to target requests. */ - headers?: Partial<{ + headers?: { /** @visibility secret */ - Authorization: string; + Authorization?: string; /** @visibility secret */ - authorization: string; + authorization?: string; /** @visibility secret */ - 'X-Api-Key': string; + 'X-Api-Key'?: string; /** @visibility secret */ - 'x-api-key': string; - [key: string]: string; - }>; + 'x-api-key'?: string; + [key: string]: string | undefined; + }; /** * Changes the origin of the host header to the target URL. Default: true. */ From 787e747d11fd95e1d2d975e6fe0371f863886d24 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 28 Jul 2023 16:06:36 +0200 Subject: [PATCH 41/64] removed usage of RegExp in configuration schemas Signed-off-by: Patrik Oldsberg --- .../config.d.ts | 16 ++++++++-------- .../config.d.ts | 16 ++++++++-------- .../catalog-backend-module-gitlab/config.d.ts | 6 +++--- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts b/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts index 9e97597724..fa7c070042 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts @@ -45,15 +45,15 @@ export interface Config { */ filters?: { /** - * (Optional) Filter for the repository slug. + * (Optional) Regular expression filter for the repository slug. * @visibility frontend */ - repoSlug?: RegExp; + repoSlug?: string; /** - * (Optional) Filter for the project key. + * (Optional) Regular expression filter for the project key. * @visibility frontend */ - projectKey?: RegExp; + projectKey?: string; }; /** * (Optional) TaskScheduleDefinition for the discovery. @@ -78,15 +78,15 @@ export interface Config { */ filters?: { /** - * (Optional) Filter for the repository slug. + * (Optional) Regular expression filter for the repository slug. * @visibility frontend */ - repoSlug?: RegExp; + repoSlug?: string; /** - * (Optional) Filter for the project key. + * (Optional) Regular expression filter for the project key. * @visibility frontend */ - projectKey?: RegExp; + projectKey?: string; }; /** * (Optional) TaskScheduleDefinition for the discovery. diff --git a/plugins/catalog-backend-module-bitbucket-server/config.d.ts b/plugins/catalog-backend-module-bitbucket-server/config.d.ts index 0554d62df3..57a94550e9 100644 --- a/plugins/catalog-backend-module-bitbucket-server/config.d.ts +++ b/plugins/catalog-backend-module-bitbucket-server/config.d.ts @@ -37,15 +37,15 @@ export interface Config { */ filters?: { /** - * (Optional) Filter for the repository slug. + * (Optional) Regular expression filter for the repository slug. * @visibility frontend */ - repoSlug?: RegExp; + repoSlug?: string; /** - * (Optional) Filter for the project key. + * (Optional) Regular expression filter for the project key. * @visibility frontend */ - projectKey?: RegExp; + projectKey?: string; }; /** * (Optional) TaskScheduleDefinition for the refresh. @@ -65,15 +65,15 @@ export interface Config { */ filters?: { /** - * (Optional) Filter for the repository slug. + * (Optional) Regular expression filter for the repository slug. * @visibility frontend */ - repoSlug?: RegExp; + repoSlug?: string; /** - * (Optional) Filter for the project key. + * (Optional) Regular expression filter for the project key. * @visibility frontend */ - projectKey?: RegExp; + projectKey?: string; }; /** * (Optional) TaskScheduleDefinition for the refresh. diff --git a/plugins/catalog-backend-module-gitlab/config.d.ts b/plugins/catalog-backend-module-gitlab/config.d.ts index e9732c0642..7d183b6e58 100644 --- a/plugins/catalog-backend-module-gitlab/config.d.ts +++ b/plugins/catalog-backend-module-gitlab/config.d.ts @@ -50,15 +50,15 @@ export interface Config { /** * (Optional) RegExp for the Project Name Pattern */ - projectPattern?: RegExp; + projectPattern?: string; /** * (Optional) RegExp for the User Name Pattern */ - userPattern?: RegExp; + userPattern?: string; /** * (Optional) RegExp for the Group Name Pattern */ - groupPattern?: RegExp; + groupPattern?: string; /** * (Optional) Skip forked repository */ From 4b82382ed8c24f7e7fed5c049ca140607fbdb10c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 28 Jul 2023 16:08:04 +0200 Subject: [PATCH 42/64] added changeset for fixed configuration schemas Signed-off-by: Patrik Oldsberg --- .changeset/neat-coins-raise.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .changeset/neat-coins-raise.md diff --git a/.changeset/neat-coins-raise.md b/.changeset/neat-coins-raise.md new file mode 100644 index 0000000000..c3045634bf --- /dev/null +++ b/.changeset/neat-coins-raise.md @@ -0,0 +1,17 @@ +--- +'@backstage/plugin-catalog-backend-module-bitbucket-server': patch +'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch +'@backstage/plugin-catalog-backend-module-puppetdb': patch +'@backstage/plugin-catalog-backend-module-msgraph': patch +'@backstage/plugin-catalog-backend-module-gerrit': patch +'@backstage/plugin-catalog-backend-module-github': patch +'@backstage/plugin-catalog-backend-module-gitlab': patch +'@backstage/plugin-events-backend-module-aws-sqs': patch +'@backstage/plugin-catalog-backend-module-azure': patch +'@backstage/plugin-catalog-backend-module-aws': patch +'@backstage/backend-common': patch +'@backstage/plugin-jenkins-backend': patch +'@backstage/plugin-proxy-backend': patch +--- + +Fixed invalid configuration schema. The configuration schema may be more strict as a result. From 7fc676a876aca92ee9b5e082597db3294178a9dd Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 28 Jul 2023 16:18:24 +0200 Subject: [PATCH 43/64] config-loader: bit more explanation around why we reject invalid schema Signed-off-by: Patrik Oldsberg --- packages/config-loader/src/schema/collect.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/config-loader/src/schema/collect.ts b/packages/config-loader/src/schema/collect.ts index 6c9737f7b7..7120c12d90 100644 --- a/packages/config-loader/src/schema/collect.ts +++ b/packages/config-loader/src/schema/collect.ts @@ -194,6 +194,12 @@ async function compileTsSchemas(paths: string[]) { [path.split(sep).join('/')], // Unix paths are expected for all OSes here ); + // All schemas should export a `Config` symbol + value = generator?.getSchemaForSymbol('Config') as JsonObject | null; + + // This makes sure that no additional symbols are defined in the schema. We don't allow + // this because they share a global namespace and will be merged together, leading to + // unpredictable behavior. const userSymbols = new Set(generator?.getUserSymbols()); userSymbols.delete('Config'); if (userSymbols.size !== 0) { @@ -203,9 +209,8 @@ async function compileTsSchemas(paths: string[]) { ); } - // All schemas should export a `Config` symbol - value = generator?.getSchemaForSymbol('Config') as JsonObject | null; - + // This makes sure that no unsupported types are used in the schema, for example `Record<,>`. + // The generator will extract these as a schema reference, which will in turn be broken for our usage. const reffedDefs = Object.keys(generator?.ReffedDefinitions ?? {}); if (reffedDefs.length !== 0) { const lines = reffedDefs.join(`${EOL} `); From bb79b1f85220d29d54c1ca6e3a5b116cda0a1431 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Jul 2023 16:13:47 +0000 Subject: [PATCH 44/64] chore(deps): update dependency @types/dagre to v0.7.49 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index eec689ba6a..e53ad8a96f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16863,9 +16863,9 @@ __metadata: linkType: hard "@types/dagre@npm:^0.7.44": - version: 0.7.48 - resolution: "@types/dagre@npm:0.7.48" - checksum: 9d06fc08219056db7c55041bf2099cea24fe824d8c8741ae11c365c7464502d8c65273153446a5546b4a92bc0833802ac1c6bddb708bfcaa75af3963e7fc84aa + version: 0.7.49 + resolution: "@types/dagre@npm:0.7.49" + checksum: cb27683074f8c89c073d0b7b549692b67ddae7225a2b6f9586d75c11598f7bd32d9246ecb184017a55592e7daaf63e4d33dcbc56ca4c3999cf34352460ddf772 languageName: node linkType: hard From f21842db06dc723f11bb3fe1bb9fa68ad5e17452 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Jul 2023 16:51:57 +0000 Subject: [PATCH 45/64] chore(deps): update dependency @types/lodash to v4.14.196 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index eec689ba6a..8056ee0919 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17379,9 +17379,9 @@ __metadata: linkType: hard "@types/lodash@npm:^4.14.151, @types/lodash@npm:^4.14.173, @types/lodash@npm:^4.14.175": - version: 4.14.195 - resolution: "@types/lodash@npm:4.14.195" - checksum: 39b75ca635b3fa943d17d3d3aabc750babe4c8212485a4df166fe0516e39288e14b0c60afc6e21913cc0e5a84734633c71e617e2bd14eaa1cf51b8d7799c432e + version: 4.14.196 + resolution: "@types/lodash@npm:4.14.196" + checksum: 201d17c3e62ae02a93c99ec78e024b2be9bd75564dd8fd8c26f6ac51a985ab280d28ce2688c3bcdfe785b0991cd9814edff19ee000234c7b45d9a697f09feb6a languageName: node linkType: hard From 83c5750b3486eda7ced778167e8d3a95bbd56544 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Jul 2023 17:10:06 +0000 Subject: [PATCH 46/64] chore(deps): update dependency @types/node to v20.4.5 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index e53ad8a96f..852e42bfcd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17538,9 +17538,9 @@ __metadata: linkType: hard "@types/node@npm:*, @types/node@npm:>=12.12.47, @types/node@npm:>=13.7.0": - version: 20.4.4 - resolution: "@types/node@npm:20.4.4" - checksum: 43f3c4a8acc38ae753e15a0e79bae0447d255b3742fa87f8e065d7b9d20ecb0e03d6c5b46c00d5d26f4552160381a00255f49205595a8ee48c2423e00263c930 + version: 20.4.5 + resolution: "@types/node@npm:20.4.5" + checksum: 36a0304a8dc346a1b2d2edac4c4633eecf70875793d61a5274d0df052d7a7af7a8e34f29884eac4fbd094c4f0201477dcb39c0ecd3307ca141688806538d1138 languageName: node linkType: hard @@ -17580,9 +17580,9 @@ __metadata: linkType: hard "@types/node@npm:^18.11.17": - version: 18.17.0 - resolution: "@types/node@npm:18.17.0" - checksum: 3a43c5c5541342751b514485144818a515fac5427f663066068eaacbe8a108cbe1207aae75ec89d34c3b32414c334aad84e9083cf7fcf3ebfd970adc871314a4 + version: 18.17.1 + resolution: "@types/node@npm:18.17.1" + checksum: 56201bda9a2d05d68602df63b4e67b0545ac8c6d0280bd5fb31701350a978a577a027501fbf49db99bf177f2242ebd1244896bfd35e89042d5bd7dfebff28d4e languageName: node linkType: hard From 223ff654f4186754874b2dcd84b77ec3f338a527 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Jul 2023 17:10:58 +0000 Subject: [PATCH 47/64] chore(deps): update dependency @types/node-forge to v1.3.4 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index e53ad8a96f..b86d5320cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17529,11 +17529,11 @@ __metadata: linkType: hard "@types/node-forge@npm:^1.3.0": - version: 1.3.3 - resolution: "@types/node-forge@npm:1.3.3" + version: 1.3.4 + resolution: "@types/node-forge@npm:1.3.4" dependencies: "@types/node": "*" - checksum: 36e3b597517ad9007822b0a0c4d44a8bcbdc4e8d8c388839d17bfeb8ff005efc1f8d3e997a1921bc54edf530000675d555f3ccb9aac8ab273285a8677a9dcb0b + checksum: c3c53ee5039a10d724bfc64fe859106baf0e39c4cac90a5d0ddd3480bf044de6ce145f104d4fa0184c365dff59df7ab4b754172c9d87800f751d10ccd7dcd1ad languageName: node linkType: hard From 1e946076d461818b837f9d3aaf108ec1d33e2a77 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Jul 2023 18:08:10 +0000 Subject: [PATCH 48/64] chore(deps): update dependency @types/testing-library__jest-dom to v5.14.9 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 907c31e02b..e89e43f4a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18217,11 +18217,11 @@ __metadata: linkType: hard "@types/testing-library__jest-dom@npm:^5.9.1": - version: 5.14.8 - resolution: "@types/testing-library__jest-dom@npm:5.14.8" + version: 5.14.9 + resolution: "@types/testing-library__jest-dom@npm:5.14.9" dependencies: "@types/jest": "*" - checksum: 18f5ba7d0db8ebd91667b544537762ce63f11c4fd02b3d57afa92f9457a384d3ecf9bc7b50b6b445af1b3ea38b69862b4f95130720d4dd23621d598ac074d93a + checksum: d364494fc2545316292e88861146146af1e3818792ca63b62a63758b2f737669b687f4aaddfcfbcb7d0e1ed7890a9bd05de23ff97f277d5e68de574497a9ee72 languageName: node linkType: hard From afb6100e6e9b5cf0b4d2438d1d41e61d1e7bf449 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Jul 2023 18:08:56 +0000 Subject: [PATCH 49/64] chore(deps): update dependency esbuild to v0.18.17 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 182 +++++++++++++++++++++++++++--------------------------- 1 file changed, 91 insertions(+), 91 deletions(-) diff --git a/yarn.lock b/yarn.lock index 907c31e02b..1cafd976df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10432,9 +10432,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/android-arm64@npm:0.18.16" +"@esbuild/android-arm64@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/android-arm64@npm:0.18.17" conditions: os=android & cpu=arm64 languageName: node linkType: hard @@ -10453,9 +10453,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/android-arm@npm:0.18.16" +"@esbuild/android-arm@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/android-arm@npm:0.18.17" conditions: os=android & cpu=arm languageName: node linkType: hard @@ -10467,9 +10467,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-x64@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/android-x64@npm:0.18.16" +"@esbuild/android-x64@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/android-x64@npm:0.18.17" conditions: os=android & cpu=x64 languageName: node linkType: hard @@ -10481,9 +10481,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/darwin-arm64@npm:0.18.16" +"@esbuild/darwin-arm64@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/darwin-arm64@npm:0.18.17" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard @@ -10495,9 +10495,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/darwin-x64@npm:0.18.16" +"@esbuild/darwin-x64@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/darwin-x64@npm:0.18.17" conditions: os=darwin & cpu=x64 languageName: node linkType: hard @@ -10509,9 +10509,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/freebsd-arm64@npm:0.18.16" +"@esbuild/freebsd-arm64@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/freebsd-arm64@npm:0.18.17" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard @@ -10523,9 +10523,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/freebsd-x64@npm:0.18.16" +"@esbuild/freebsd-x64@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/freebsd-x64@npm:0.18.17" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard @@ -10537,9 +10537,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/linux-arm64@npm:0.18.16" +"@esbuild/linux-arm64@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/linux-arm64@npm:0.18.17" conditions: os=linux & cpu=arm64 languageName: node linkType: hard @@ -10551,9 +10551,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/linux-arm@npm:0.18.16" +"@esbuild/linux-arm@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/linux-arm@npm:0.18.17" conditions: os=linux & cpu=arm languageName: node linkType: hard @@ -10565,9 +10565,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/linux-ia32@npm:0.18.16" +"@esbuild/linux-ia32@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/linux-ia32@npm:0.18.17" conditions: os=linux & cpu=ia32 languageName: node linkType: hard @@ -10586,9 +10586,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/linux-loong64@npm:0.18.16" +"@esbuild/linux-loong64@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/linux-loong64@npm:0.18.17" conditions: os=linux & cpu=loong64 languageName: node linkType: hard @@ -10600,9 +10600,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/linux-mips64el@npm:0.18.16" +"@esbuild/linux-mips64el@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/linux-mips64el@npm:0.18.17" conditions: os=linux & cpu=mips64el languageName: node linkType: hard @@ -10614,9 +10614,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/linux-ppc64@npm:0.18.16" +"@esbuild/linux-ppc64@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/linux-ppc64@npm:0.18.17" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard @@ -10628,9 +10628,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/linux-riscv64@npm:0.18.16" +"@esbuild/linux-riscv64@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/linux-riscv64@npm:0.18.17" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard @@ -10642,9 +10642,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/linux-s390x@npm:0.18.16" +"@esbuild/linux-s390x@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/linux-s390x@npm:0.18.17" conditions: os=linux & cpu=s390x languageName: node linkType: hard @@ -10656,9 +10656,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/linux-x64@npm:0.18.16" +"@esbuild/linux-x64@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/linux-x64@npm:0.18.17" conditions: os=linux & cpu=x64 languageName: node linkType: hard @@ -10670,9 +10670,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/netbsd-x64@npm:0.18.16" +"@esbuild/netbsd-x64@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/netbsd-x64@npm:0.18.17" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard @@ -10684,9 +10684,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/openbsd-x64@npm:0.18.16" +"@esbuild/openbsd-x64@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/openbsd-x64@npm:0.18.17" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard @@ -10698,9 +10698,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/sunos-x64@npm:0.18.16" +"@esbuild/sunos-x64@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/sunos-x64@npm:0.18.17" conditions: os=sunos & cpu=x64 languageName: node linkType: hard @@ -10712,9 +10712,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/win32-arm64@npm:0.18.16" +"@esbuild/win32-arm64@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/win32-arm64@npm:0.18.17" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard @@ -10726,9 +10726,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/win32-ia32@npm:0.18.16" +"@esbuild/win32-ia32@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/win32-ia32@npm:0.18.17" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard @@ -10740,9 +10740,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.18.16": - version: 0.18.16 - resolution: "@esbuild/win32-x64@npm:0.18.16" +"@esbuild/win32-x64@npm:0.18.17": + version: 0.18.17 + resolution: "@esbuild/win32-x64@npm:0.18.17" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -24327,31 +24327,31 @@ __metadata: linkType: hard "esbuild@npm:^0.18.0": - version: 0.18.16 - resolution: "esbuild@npm:0.18.16" + version: 0.18.17 + resolution: "esbuild@npm:0.18.17" dependencies: - "@esbuild/android-arm": 0.18.16 - "@esbuild/android-arm64": 0.18.16 - "@esbuild/android-x64": 0.18.16 - "@esbuild/darwin-arm64": 0.18.16 - "@esbuild/darwin-x64": 0.18.16 - "@esbuild/freebsd-arm64": 0.18.16 - "@esbuild/freebsd-x64": 0.18.16 - "@esbuild/linux-arm": 0.18.16 - "@esbuild/linux-arm64": 0.18.16 - "@esbuild/linux-ia32": 0.18.16 - "@esbuild/linux-loong64": 0.18.16 - "@esbuild/linux-mips64el": 0.18.16 - "@esbuild/linux-ppc64": 0.18.16 - "@esbuild/linux-riscv64": 0.18.16 - "@esbuild/linux-s390x": 0.18.16 - "@esbuild/linux-x64": 0.18.16 - "@esbuild/netbsd-x64": 0.18.16 - "@esbuild/openbsd-x64": 0.18.16 - "@esbuild/sunos-x64": 0.18.16 - "@esbuild/win32-arm64": 0.18.16 - "@esbuild/win32-ia32": 0.18.16 - "@esbuild/win32-x64": 0.18.16 + "@esbuild/android-arm": 0.18.17 + "@esbuild/android-arm64": 0.18.17 + "@esbuild/android-x64": 0.18.17 + "@esbuild/darwin-arm64": 0.18.17 + "@esbuild/darwin-x64": 0.18.17 + "@esbuild/freebsd-arm64": 0.18.17 + "@esbuild/freebsd-x64": 0.18.17 + "@esbuild/linux-arm": 0.18.17 + "@esbuild/linux-arm64": 0.18.17 + "@esbuild/linux-ia32": 0.18.17 + "@esbuild/linux-loong64": 0.18.17 + "@esbuild/linux-mips64el": 0.18.17 + "@esbuild/linux-ppc64": 0.18.17 + "@esbuild/linux-riscv64": 0.18.17 + "@esbuild/linux-s390x": 0.18.17 + "@esbuild/linux-x64": 0.18.17 + "@esbuild/netbsd-x64": 0.18.17 + "@esbuild/openbsd-x64": 0.18.17 + "@esbuild/sunos-x64": 0.18.17 + "@esbuild/win32-arm64": 0.18.17 + "@esbuild/win32-ia32": 0.18.17 + "@esbuild/win32-x64": 0.18.17 dependenciesMeta: "@esbuild/android-arm": optional: true @@ -24399,7 +24399,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 8b04f7087d4e95cffd43c37663c57a9f42e183cd3d02fe3cefee7e1534a84d5cec181f42187715471b4b3f5478e2e110530df7d3a8b12053cbc8cc35145363ab + checksum: c6e1ffa776978a45697763a07ec9b16411db3d3b3997b2c4a0165a211727fce8b63b87165a28d8ef60d3a28b98197bbbc2833e51b89888a4437e0a483dffc8ff languageName: node linkType: hard From 789c6d9b901f2db65beb41e1a860457122e4c656 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Jul 2023 19:21:30 +0000 Subject: [PATCH 50/64] chore(deps): update dependency xml2js to v0.6.2 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index e89e43f4a9..70785fc251 100644 --- a/yarn.lock +++ b/yarn.lock @@ -42711,12 +42711,12 @@ __metadata: linkType: hard "xml2js@npm:^0.6.0": - version: 0.6.0 - resolution: "xml2js@npm:0.6.0" + version: 0.6.2 + resolution: "xml2js@npm:0.6.2" dependencies: sax: ">=0.6.0" xmlbuilder: ~11.0.0 - checksum: 437f353fd66d367bf158e9555a0625df9965d944e499728a5c6bc92a54a2763179b144f14b7e1c725040f56bbd22b0fa6cfcb09ec4faf39c45ce01efe631f40b + checksum: 458a83806193008edff44562c0bdb982801d61ee7867ae58fd35fab781e69e17f40dfeb8fc05391a4648c9c54012066d3955fe5d993ffbe4dc63399023f32ac2 languageName: node linkType: hard From 544ecda62faa112e75403addccb863a064012092 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Jul 2023 19:21:59 +0000 Subject: [PATCH 51/64] fix(deps): update dependency @roadiehq/backstage-plugin-travis-ci to v2.1.13 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/yarn.lock b/yarn.lock index e89e43f4a9..fb4c774440 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9678,7 +9678,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/theme@^0.4.0, @backstage/theme@^0.4.1, @backstage/theme@workspace:^, @backstage/theme@workspace:packages/theme": +"@backstage/theme@^0.4.1, @backstage/theme@workspace:^, @backstage/theme@workspace:packages/theme": version: 0.0.0-use.local resolution: "@backstage/theme@workspace:packages/theme" dependencies: @@ -14410,14 +14410,14 @@ __metadata: linkType: hard "@roadiehq/backstage-plugin-travis-ci@npm:^2.0.5": - version: 2.1.12 - resolution: "@roadiehq/backstage-plugin-travis-ci@npm:2.1.12" + version: 2.1.13 + resolution: "@roadiehq/backstage-plugin-travis-ci@npm:2.1.13" dependencies: - "@backstage/catalog-model": ^1.4.0 - "@backstage/core-components": ^0.13.2 - "@backstage/core-plugin-api": ^1.5.2 - "@backstage/plugin-catalog-react": ^1.7.0 - "@backstage/theme": ^0.4.0 + "@backstage/catalog-model": ^1.4.1 + "@backstage/core-components": ^0.13.3 + "@backstage/core-plugin-api": ^1.5.3 + "@backstage/plugin-catalog-react": ^1.8.0 + "@backstage/theme": ^0.4.1 "@material-ui/core": ^4.11.3 "@material-ui/icons": ^4.11.2 "@material-ui/lab": 4.0.0-alpha.57 @@ -14431,7 +14431,7 @@ __metadata: react-dom: ^16.13.1 || ^17.0.0 react-router: 6.0.0-beta.0 || ^6.3.0 react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: e4e5b91323104ea7fe6608333960d92f7670fc1f6372c5942ca35cbaeb48f542dc7162f7e7a81f859a353124e6b94e0be776f43e690c8f9fa66f66986dca97f6 + checksum: c8ddeadcc5edec0b25276b65ddf7971943939eb2c82d363919c748b8a740ed53c2642bda6cb509d7fc8c02f002166abf8fc4cb7a2cfa83577e1139c5ba31841b languageName: node linkType: hard From 9dad4b0e61bd4b0e5080b8b5af8842977512cc21 Mon Sep 17 00:00:00 2001 From: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Date: Fri, 28 Jul 2023 14:52:35 -0500 Subject: [PATCH 52/64] Config validation clean up Signed-off-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> --- .changeset/popular-fans-camp.md | 5 +++++ app-config.yaml | 8 ++++---- plugins/auth-backend/config.d.ts | 2 ++ 3 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 .changeset/popular-fans-camp.md diff --git a/.changeset/popular-fans-camp.md b/.changeset/popular-fans-camp.md new file mode 100644 index 0000000000..47fc095ef8 --- /dev/null +++ b/.changeset/popular-fans-camp.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Updated config schema to match what was being used in code diff --git a/app-config.yaml b/app-config.yaml index 2b562ee689..8dcd052e61 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -211,7 +211,7 @@ integrations: # clientEmail: 'example@example.com' # privateKey: ${GCS_PRIVATE_KEY} awsS3: - - host: amazonaws.com + - endpoint: ${AWS_S3_ENDPOINT} accessKeyId: ${AWS_ACCESS_KEY_ID} secretAccessKey: ${AWS_SECRET_ACCESS_KEY} @@ -352,8 +352,6 @@ auth: metadataUrl: ${AUTH_OIDC_METADATA_URL} clientId: ${AUTH_OIDC_CLIENT_ID} clientSecret: ${AUTH_OIDC_CLIENT_SECRET} - authorizationUrl: ${AUTH_OIDC_AUTH_URL} - tokenUrl: ${AUTH_OIDC_TOKEN_URL} tokenEndpointAuthMethod: ${AUTH_OIDC_TOKEN_ENDPOINT_AUTH_METHOD} # default='client_secret_basic' tokenSignedResponseAlg: ${AUTH_OIDC_TOKEN_SIGNED_RESPONSE_ALG} # default='RS256' scope: ${AUTH_OIDC_SCOPE} # default='openid profile email' @@ -434,8 +432,10 @@ costInsights: kind: 'PINTS_OF_ICE_CREAM' unit: 'ice cream pint' rate: 5.5 -pagerduty: + +pagerDuty: eventsBaseUrl: 'https://events.pagerduty.com/v2' + jenkins: instances: - name: default diff --git a/plugins/auth-backend/config.d.ts b/plugins/auth-backend/config.d.ts index c6579e08f8..d059f2ec04 100644 --- a/plugins/auth-backend/config.d.ts +++ b/plugins/auth-backend/config.d.ts @@ -152,6 +152,8 @@ export interface Config { clientSecret: string; callbackUrl?: string; metadataUrl: string; + tokenEndpointAuthMethod?: string; + tokenSignedResponseAlg?: string; scope?: string; prompt?: string; }; From b4222908b0c38ccff0dba6df2b7197f3659b99de Mon Sep 17 00:00:00 2001 From: Diego Bardari Date: Thu, 27 Jul 2023 15:55:46 +0200 Subject: [PATCH 53/64] Added option to configure AWS accountId in AwsS3EntityProvider Signed-off-by: Diego Bardari --- .changeset/stale-wombats-talk.md | 5 +++++ plugins/catalog-backend-module-aws/config.d.ts | 12 ++++++++++++ .../src/providers/AwsS3EntityProvider.ts | 10 ++++++---- .../src/providers/config.ts | 2 ++ .../src/providers/types.ts | 1 + 5 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 .changeset/stale-wombats-talk.md diff --git a/.changeset/stale-wombats-talk.md b/.changeset/stale-wombats-talk.md new file mode 100644 index 0000000000..f4e0b852f8 --- /dev/null +++ b/.changeset/stale-wombats-talk.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-aws': patch +--- + +Added option to configure AWS `accountId` in `AwsS3EntityProvider` diff --git a/plugins/catalog-backend-module-aws/config.d.ts b/plugins/catalog-backend-module-aws/config.d.ts index b3610ccfc5..6a45b6636f 100644 --- a/plugins/catalog-backend-module-aws/config.d.ts +++ b/plugins/catalog-backend-module-aws/config.d.ts @@ -54,6 +54,12 @@ export interface Config { * @see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-region.html */ region?: string; + /** + * (Optional) AWS Account id. + * If not set, main account is used. + * @see https://github.com/backstage/backstage/blob/master/packages/integration-aws-node/README.md + */ + accountId?: string; /** * (Optional) TaskScheduleDefinition for the refresh. */ @@ -77,6 +83,12 @@ export interface Config { * @see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-region.html */ region?: string; + /** + * (Optional) AWS Account id. + * If not set, main account is used. + * @see https://github.com/backstage/backstage/blob/master/packages/integration-aws-node/README.md + */ + accountId?: string; /** * (Optional) TaskScheduleDefinition for the refresh. */ diff --git a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts index 4a87a42e75..4df92b58c2 100644 --- a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts +++ b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts @@ -146,20 +146,22 @@ export class AwsS3EntityProvider implements EntityProvider { /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */ async connect(connection: EntityProviderConnection): Promise { this.connection = connection; - const credProvider = - await this.awsCredentialsManager.getCredentialProvider(); + const { accountId, region, bucketName } = this.config; + const credProvider = await this.awsCredentialsManager.getCredentialProvider( + accountId ? { accountId } : undefined, + ); this.s3 = new S3({ apiVersion: '2006-03-01', credentialDefaultProvider: () => credProvider.sdkCredentialProvider, endpoint: this.integration.config.endpoint, - region: this.config.region, + region, forcePathStyle: this.integration.config.s3ForcePathStyle, }); // https://github.com/aws/aws-sdk-js-v3/issues/4122#issuecomment-1298968804 const endpoint = await getEndpointFromInstructions( { - Bucket: this.config.bucketName, + Bucket: bucketName, }, ListObjectsV2Command, this.s3.config as unknown as Record, diff --git a/plugins/catalog-backend-module-aws/src/providers/config.ts b/plugins/catalog-backend-module-aws/src/providers/config.ts index 5ef2729bf0..1ab52f9fd4 100644 --- a/plugins/catalog-backend-module-aws/src/providers/config.ts +++ b/plugins/catalog-backend-module-aws/src/providers/config.ts @@ -46,6 +46,7 @@ function readAwsS3Config(id: string, config: Config): AwsS3Config { const bucketName = config.getString('bucketName'); const region = config.getOptionalString('region'); const prefix = config.getOptionalString('prefix'); + const accountId = config.getOptionalString('accountId'); const schedule = config.has('schedule') ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) @@ -57,5 +58,6 @@ function readAwsS3Config(id: string, config: Config): AwsS3Config { region, prefix, schedule, + accountId, }; } diff --git a/plugins/catalog-backend-module-aws/src/providers/types.ts b/plugins/catalog-backend-module-aws/src/providers/types.ts index 204fa154c6..8e12b5f096 100644 --- a/plugins/catalog-backend-module-aws/src/providers/types.ts +++ b/plugins/catalog-backend-module-aws/src/providers/types.ts @@ -22,4 +22,5 @@ export type AwsS3Config = { prefix?: string; region?: string; schedule?: TaskScheduleDefinition; + accountId?: string; }; From d9922197f10f6105578d5619f10c345379233eae Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Jul 2023 20:13:05 +0000 Subject: [PATCH 54/64] fix(deps): update dependency @stoplight/spectral-core to v1.18.3 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4990ea8856..c20e33f57d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15293,7 +15293,7 @@ __metadata: languageName: node linkType: hard -"@stoplight/json@npm:^3.17.0, @stoplight/json@npm:^3.17.1": +"@stoplight/json@npm:^3.17.0, @stoplight/json@npm:^3.17.1, @stoplight/json@npm:~3.21.0": version: 3.21.0 resolution: "@stoplight/json@npm:3.21.0" dependencies: @@ -15336,11 +15336,11 @@ __metadata: linkType: hard "@stoplight/spectral-core@npm:^1.15.1, @stoplight/spectral-core@npm:^1.18.0, @stoplight/spectral-core@npm:^1.7.0, @stoplight/spectral-core@npm:^1.8.0, @stoplight/spectral-core@npm:^1.8.1": - version: 1.18.0 - resolution: "@stoplight/spectral-core@npm:1.18.0" + version: 1.18.3 + resolution: "@stoplight/spectral-core@npm:1.18.3" dependencies: "@stoplight/better-ajv-errors": 1.0.3 - "@stoplight/json": ~3.20.1 + "@stoplight/json": ~3.21.0 "@stoplight/path": 1.3.2 "@stoplight/spectral-parsers": ^1.0.0 "@stoplight/spectral-ref-resolver": ^1.0.0 @@ -15360,7 +15360,7 @@ __metadata: pony-cause: ^1.0.0 simple-eval: 1.0.0 tslib: ^2.3.0 - checksum: 76ff9e2349c035fa2b98556c8bd67bb531f2b9c29e19e33484270d62f30900d36ceb51ef0075255479cc5071b45bcb7249088f568fff9d44cd159b5ee8f07cec + checksum: 321d868a6c1e3d5f009d87d02651b423b5b6f5ef75a2ad1937b52b8ddc6e83dc3fe9618b00d7d92407e2eb3380b8409dc6ce98a8628d50ebd60d15dc8c15a7b8 languageName: node linkType: hard From 09678cbfc1c56711528253c6a16323196fd38121 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Jul 2023 20:13:36 +0000 Subject: [PATCH 55/64] fix(deps): update dependency @stoplight/spectral-parsers to v1.0.3 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4990ea8856..89b1853460 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15293,7 +15293,7 @@ __metadata: languageName: node linkType: hard -"@stoplight/json@npm:^3.17.0, @stoplight/json@npm:^3.17.1": +"@stoplight/json@npm:^3.17.0, @stoplight/json@npm:^3.17.1, @stoplight/json@npm:~3.21.0": version: 3.21.0 resolution: "@stoplight/json@npm:3.21.0" dependencies: @@ -15414,14 +15414,14 @@ __metadata: linkType: hard "@stoplight/spectral-parsers@npm:^1.0.0, @stoplight/spectral-parsers@npm:^1.0.2": - version: 1.0.2 - resolution: "@stoplight/spectral-parsers@npm:1.0.2" + version: 1.0.3 + resolution: "@stoplight/spectral-parsers@npm:1.0.3" dependencies: - "@stoplight/json": ~3.20.1 + "@stoplight/json": ~3.21.0 "@stoplight/types": ^13.6.0 "@stoplight/yaml": ~4.2.3 tslib: ^2.3.1 - checksum: 89bc5c77ebeedca0abf9ffa9e074beed57d47b71814f23a1d65c03469aa6ea923007b42e7cb18ca7bcc4ff9fd399d604d32a41aa4c4c57a9cc3bebb4b24c3e34 + checksum: e1120e9ffc3db7f50573db08768c1206f5adc5fc503e77d3696e86e0765ce247c3c513ed12e53318b6aef0de33fc730aa78ccd542142d17d6bae01e23cba5879 languageName: node linkType: hard From 611317b5b469e87ee4cc9d2956e96bf9549e1dac Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Jul 2023 23:03:38 +0000 Subject: [PATCH 56/64] fix(deps): update dependency @uiw/react-codemirror to v4.21.9 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/yarn.lock b/yarn.lock index e1ffe2c19b..6af0a07b2e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15307,20 +15307,6 @@ __metadata: languageName: node linkType: hard -"@stoplight/json@npm:~3.20.1": - version: 3.20.3 - resolution: "@stoplight/json@npm:3.20.3" - dependencies: - "@stoplight/ordered-object-literal": ^1.0.3 - "@stoplight/path": ^1.3.2 - "@stoplight/types": ^13.6.0 - jsonc-parser: ~2.2.1 - lodash: ^4.17.21 - safe-stable-stringify: ^1.1 - checksum: 52a4251deca0be91c98172287def5cb004ff2ab801c3b7bb24f02abe14ad0890ee453588f235401fa048c5fefe1a750d7f68857f681d57afb62dd5310f50d71a - languageName: node - linkType: hard - "@stoplight/ordered-object-literal@npm:^1.0.1, @stoplight/ordered-object-literal@npm:^1.0.3": version: 1.0.4 resolution: "@stoplight/ordered-object-literal@npm:1.0.4" @@ -18584,9 +18570,9 @@ __metadata: languageName: node linkType: hard -"@uiw/codemirror-extensions-basic-setup@npm:4.21.7": - version: 4.21.7 - resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.21.7" +"@uiw/codemirror-extensions-basic-setup@npm:4.21.9": + version: 4.21.9 + resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.21.9" dependencies: "@codemirror/autocomplete": ^6.0.0 "@codemirror/commands": ^6.0.0 @@ -18603,19 +18589,19 @@ __metadata: "@codemirror/search": ">=6.0.0" "@codemirror/state": ">=6.0.0" "@codemirror/view": ">=6.0.0" - checksum: c81ccef43396c92049a7bd05fecf6e4f7e1adfaed330892ca472b0d65e1ab919dbdd28475baea833a387547fedbe9c7dcc19b92577e1e8bba42084ee86ab3944 + checksum: e7f2a78dd26a919b00247ad85211a09b8242d57585c7689cc44ea9de99e02e8fd8dd281ebf48e61c4126704b031babb5a596307299015bb0aa8e561a0ad2d9b1 languageName: node linkType: hard "@uiw/react-codemirror@npm:^4.9.3": - version: 4.21.7 - resolution: "@uiw/react-codemirror@npm:4.21.7" + version: 4.21.9 + resolution: "@uiw/react-codemirror@npm:4.21.9" dependencies: "@babel/runtime": ^7.18.6 "@codemirror/commands": ^6.1.0 "@codemirror/state": ^6.1.1 "@codemirror/theme-one-dark": ^6.0.0 - "@uiw/codemirror-extensions-basic-setup": 4.21.7 + "@uiw/codemirror-extensions-basic-setup": 4.21.9 codemirror: ^6.0.0 peerDependencies: "@babel/runtime": ">=7.11.0" @@ -18625,7 +18611,7 @@ __metadata: codemirror: ">=6.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: c4650957d77556f8c2c5b1b072d7b381c7cbec068ff2c744d2891a88d6fb425d520b7b70addc6c82e809326467dcfa913171d62977009faf41be733b079d3e1e + checksum: c09a121e0c37f0dfb720e05f080b5f420eaec9bf1502cad38a100f2cefc731f50e854d682898163ad23aaefefcb505b57f4621b9724eca39d334258e0f25fddb languageName: node linkType: hard From a931290fc3ae9ba126e1e392debb216893b19122 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Jul 2023 23:04:12 +0000 Subject: [PATCH 57/64] fix(deps): update dependency dompurify to v2.4.7 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/yarn.lock b/yarn.lock index e1ffe2c19b..4fefea383a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15307,20 +15307,6 @@ __metadata: languageName: node linkType: hard -"@stoplight/json@npm:~3.20.1": - version: 3.20.3 - resolution: "@stoplight/json@npm:3.20.3" - dependencies: - "@stoplight/ordered-object-literal": ^1.0.3 - "@stoplight/path": ^1.3.2 - "@stoplight/types": ^13.6.0 - jsonc-parser: ~2.2.1 - lodash: ^4.17.21 - safe-stable-stringify: ^1.1 - checksum: 52a4251deca0be91c98172287def5cb004ff2ab801c3b7bb24f02abe14ad0890ee453588f235401fa048c5fefe1a750d7f68857f681d57afb62dd5310f50d71a - languageName: node - linkType: hard - "@stoplight/ordered-object-literal@npm:^1.0.1, @stoplight/ordered-object-literal@npm:^1.0.3": version: 1.0.4 resolution: "@stoplight/ordered-object-literal@npm:1.0.4" @@ -23606,9 +23592,9 @@ __metadata: linkType: hard "dompurify@npm:^2.2.7, dompurify@npm:^2.2.9, dompurify@npm:^2.3.6": - version: 2.4.5 - resolution: "dompurify@npm:2.4.5" - checksum: d6d3c3b320f15cdb5b26aa1902c3275a3ab2c3705a9df4420bb94691d7c4df67959ec7b91e486c308320791b0ee000456f042734c45d76721e61c2768eac706e + version: 2.4.7 + resolution: "dompurify@npm:2.4.7" + checksum: 13c047e772a1998348191554dda403950d45ef2ec75fa0b9915cc179ccea0a39ef780d283109bd72cf83a2a085af6c77664281d4d0106a737bc5f39906364efe languageName: node linkType: hard From e3463e4d404a643a360d464b20185e9c50c31769 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 29 Jul 2023 01:17:06 +0000 Subject: [PATCH 58/64] fix(deps): update dependency eslint-plugin-jest to v27.2.3 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6af0a07b2e..d50fd04b14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24644,12 +24644,12 @@ __metadata: linkType: hard "eslint-plugin-jest@npm:^27.0.0": - version: 27.2.2 - resolution: "eslint-plugin-jest@npm:27.2.2" + version: 27.2.3 + resolution: "eslint-plugin-jest@npm:27.2.3" dependencies: "@typescript-eslint/utils": ^5.10.0 peerDependencies: - "@typescript-eslint/eslint-plugin": ^5.0.0 + "@typescript-eslint/eslint-plugin": ^5.0.0 || ^6.0.0 eslint: ^7.0.0 || ^8.0.0 jest: "*" peerDependenciesMeta: @@ -24657,7 +24657,7 @@ __metadata: optional: true jest: optional: true - checksum: 98b63252d985f5dedf36ce9587dd4a0d24daf71ca8a997258343402c0d33ddd5070502378dafd9ac7fc0ef2e0d557b5c77f18e09ad73c71a52de8061db88293f + checksum: 4c7e07f52f17749ac6fd0ff5fcd5ce30b88983ba31eeee322e4d48859f55eaa112f06172e586ad2031c00ff28bb2dfdc3d35c83895251b9c0e860fa47dfc5ff4 languageName: node linkType: hard From f9c5e56a35053184906724adbc3f16f9ca6bf4f9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 29 Jul 2023 01:17:48 +0000 Subject: [PATCH 59/64] fix(deps): update dependency isomorphic-git to v1.24.5 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6af0a07b2e..658927de2d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28920,8 +28920,8 @@ __metadata: linkType: hard "isomorphic-git@npm:^1.23.0": - version: 1.24.2 - resolution: "isomorphic-git@npm:1.24.2" + version: 1.24.5 + resolution: "isomorphic-git@npm:1.24.5" dependencies: async-lock: ^1.1.0 clean-git-ref: ^2.0.1 @@ -28936,7 +28936,7 @@ __metadata: simple-get: ^4.0.1 bin: isogit: cli.cjs - checksum: 5c69ebf32624ac175fda11b49970e235dd380f18d5ebb80580e1c1b9649bb95aa5f02dc9d2967acf99f485e40f558659c0d14f1ee87bbcc06330a1fcfa5ffb93 + checksum: d9d13d76ec3a7d7cc8afd70d07ea920f07806b74810fe414559d460cbef8d49c7e0f6dfba0e1773c7856b1d3dd1bf98e17a09ae6aa4a8fd2d759a2f54989491a languageName: node linkType: hard From f2a0fe664747ad9cf06d81267794016dc1f25385 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 29 Jul 2023 02:16:14 +0000 Subject: [PATCH 60/64] fix(deps): update dependency keyv to v4.5.3 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index d50fd04b14..fea9629198 100644 --- a/yarn.lock +++ b/yarn.lock @@ -30539,11 +30539,11 @@ __metadata: linkType: hard "keyv@npm:^4.0.0, keyv@npm:^4.5.2": - version: 4.5.2 - resolution: "keyv@npm:4.5.2" + version: 4.5.3 + resolution: "keyv@npm:4.5.3" dependencies: json-buffer: 3.0.1 - checksum: 13ad58303acd2261c0d4831b4658451603fd159e61daea2121fcb15feb623e75ee328cded0572da9ca76b7b3ceaf8e614f1806c6b3af5db73c9c35a345259651 + checksum: 3ffb4d5b72b6b4b4af443bbb75ca2526b23c750fccb5ac4c267c6116888b4b65681015c2833cb20d26cf3e6e32dac6b988c77f7f022e1a571b7d90f1442257da languageName: node linkType: hard From 6cd88c35e86ef1b3ed80902cc266532c9a6e50b4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 29 Jul 2023 02:36:06 +0000 Subject: [PATCH 61/64] fix(deps): update dependency openid-client to v5.4.3 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4022b09061..a1d4977377 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29668,10 +29668,10 @@ __metadata: languageName: node linkType: hard -"jose@npm:^4.14.1, jose@npm:^4.6.0": - version: 4.14.3 - resolution: "jose@npm:4.14.3" - checksum: b766d65a60c0407f891003aae053fb500be0edb7e0745e6702bce39199d9f06fda5eb74a90df10611cb7bd7daf2636cbe3726341fef388b51cb4e43b64200f4f +"jose@npm:^4.14.4, jose@npm:^4.6.0": + version: 4.14.4 + resolution: "jose@npm:4.14.4" + checksum: 2d820a91a8fd97c05d8bc8eedc373b944a0cd7f5fe41063086da233d0473c73fb523912a9f026ea870782bd221f4a515f441a2d3af4de48c6f2c76dac5082377 languageName: node linkType: hard @@ -33921,14 +33921,14 @@ __metadata: linkType: hard "openid-client@npm:^5.2.1, openid-client@npm:^5.3.0": - version: 5.4.2 - resolution: "openid-client@npm:5.4.2" + version: 5.4.3 + resolution: "openid-client@npm:5.4.3" dependencies: - jose: ^4.14.1 + jose: ^4.14.4 lru-cache: ^6.0.0 object-hash: ^2.2.0 oidc-token-hash: ^5.0.3 - checksum: 0f3570990a4979aff8581de35078c82d21d45baed9805e0e385dfc62c24fa295ee82d86386846a33bc33ed3b524a5406d8564f9f927420027719f84bf1d8b741 + checksum: 0e5a126b77dad0320e8f7023ac7ad7f5f1f82ad5f985f7ab0b42a7cf36700dfb78f0bef9b59c1fae915dce0148ef191b49921cd0a01443b64c04f862d9dc03e0 languageName: node linkType: hard From c6e220a3aba5cd52fbfe0c2747237e9c49faab80 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 29 Jul 2023 03:09:44 +0000 Subject: [PATCH 62/64] fix(deps): update dependency postcss to v8.4.27 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index a8a8edc7ce..0cf971d206 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35544,13 +35544,13 @@ __metadata: linkType: hard "postcss@npm:^8.1.0, postcss@npm:^8.4.21": - version: 8.4.24 - resolution: "postcss@npm:8.4.24" + version: 8.4.27 + resolution: "postcss@npm:8.4.27" dependencies: nanoid: ^3.3.6 picocolors: ^1.0.0 source-map-js: ^1.0.2 - checksum: 814e2126dacfea313588eda09cc99a9b4c26ec55c059188aa7a916d20d26d483483106dc5ff9e560731b59f45c5bb91b945dfadc670aed875cc90ddbbf4e787d + checksum: 1cdd0c298849df6cd65f7e646a3ba36870a37b65f55fd59d1a165539c263e9b4872a402bf4ed1ca1bc31f58b68b2835545e33ea1a23b161a1f8aa6d5ded81e78 languageName: node linkType: hard From c03129e54d0e21fdbe01d55e09f7c74c0ac97366 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 29 Jul 2023 03:51:28 +0000 Subject: [PATCH 63/64] fix(deps): update dependency semver to v7.5.4 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0cf971d206..fde6e19c43 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38456,11 +38456,11 @@ __metadata: linkType: hard "semver@npm:2 || 3 || 4 || 5, semver@npm:^5.4.1, semver@npm:^5.5.0, semver@npm:^5.6.0, semver@npm:^5.7.1": - version: 5.7.1 - resolution: "semver@npm:5.7.1" + version: 5.7.2 + resolution: "semver@npm:5.7.2" bin: - semver: ./bin/semver - checksum: 57fd0acfd0bac382ee87cd52cd0aaa5af086a7dc8d60379dfe65fea491fb2489b6016400813930ecd61fd0952dae75c115287a1b16c234b1550887117744dfaf + semver: bin/semver + checksum: fb4ab5e0dd1c22ce0c937ea390b4a822147a9c53dbd2a9a0132f12fe382902beef4fbf12cf51bb955248d8d15874ce8cd89532569756384f994309825f10b686 languageName: node linkType: hard @@ -38474,22 +38474,22 @@ __metadata: linkType: hard "semver@npm:^6.0.0, semver@npm:^6.1.1, semver@npm:^6.1.2, semver@npm:^6.3.0": - version: 6.3.0 - resolution: "semver@npm:6.3.0" + version: 6.3.1 + resolution: "semver@npm:6.3.1" bin: - semver: ./bin/semver.js - checksum: 1b26ecf6db9e8292dd90df4e781d91875c0dcc1b1909e70f5d12959a23c7eebb8f01ea581c00783bbee72ceeaad9505797c381756326073850dc36ed284b21b9 + semver: bin/semver.js + checksum: ae47d06de28836adb9d3e25f22a92943477371292d9b665fb023fae278d345d508ca1958232af086d85e0155aee22e313e100971898bbb8d5d89b8b1d4054ca2 languageName: node linkType: hard "semver@npm:^7.1.1, semver@npm:^7.1.2, semver@npm:^7.1.3, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.0, semver@npm:^7.5.3": - version: 7.5.3 - resolution: "semver@npm:7.5.3" + version: 7.5.4 + resolution: "semver@npm:7.5.4" dependencies: lru-cache: ^6.0.0 bin: semver: bin/semver.js - checksum: 9d58db16525e9f749ad0a696a1f27deabaa51f66e91d2fa2b0db3de3e9644e8677de3b7d7a03f4c15bc81521e0c3916d7369e0572dbde250d9bedf5194e2a8a7 + checksum: 12d8ad952fa353b0995bf180cdac205a4068b759a140e5d3c608317098b3575ac2f1e09182206bf2eb26120e1c0ed8fb92c48c592f6099680de56bb071423ca3 languageName: node linkType: hard From eea2922e749ab0c6b66b3cae9231d5f468c062a5 Mon Sep 17 00:00:00 2001 From: Alexandr Puzeyev <1099257+Tirex@users.noreply.github.com> Date: Mon, 31 Jul 2023 10:48:27 +0600 Subject: [PATCH 64/64] README update - adding example of apiRef definition and fixed component name Signed-off-by: Alexandr Puzeyev <1099257+Tirex@users.noreply.github.com> --- .changeset/wild-timers-sparkle.md | 5 +++++ plugins/microsoft-calendar/README.md | 27 +++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 .changeset/wild-timers-sparkle.md diff --git a/.changeset/wild-timers-sparkle.md b/.changeset/wild-timers-sparkle.md new file mode 100644 index 0000000000..3df14f3a69 --- /dev/null +++ b/.changeset/wild-timers-sparkle.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-microsoft-calendar': patch +--- + +README update - example of apiRef definition and fixed component name diff --git a/plugins/microsoft-calendar/README.md b/plugins/microsoft-calendar/README.md index 92421046e2..3dcf58c418 100644 --- a/plugins/microsoft-calendar/README.md +++ b/plugins/microsoft-calendar/README.md @@ -32,15 +32,38 @@ yarn add --cwd packages/app @backstage/plugin-microsoft-calendar 3. You can then use the provided React component `MicrosoftCalendar` in the backstage frontend where ever you want ```tsx -import { MicrosoftCalendar } from '@backstage/plugin-microsoft-calendar'; +import { MicrosoftCalendarCard } from '@backstage/plugin-microsoft-calendar'; // ... - + ; // ... ``` +If your homepage is not static JSX add `microsoftCalendarApiRef` to the App's `apis.ts`: + +```ts +import { + MicrosoftCalendarApiClient, + microsoftCalendarApiRef, +} from '@backstage/plugin-microsoft-calendar'; +import { + // ... + fetchApiRef, + // ... +} from '@backstage/core-plugin-api'; + +export const apis = [ + // ... + createApiFactory({ + api: microsoftCalendarApiRef, + deps: { authApi: microsoftAuthApiRef, fetchApi: fetchApiRef }, + factory: deps => new MicrosoftCalendarApiClient(deps), + }), +]; +``` + ![Microsoft Calendar plugin demo](https://user-images.githubusercontent.com/23618736/215717491-25db5fa6-b237-487f-8c00-28f572e8da05.mp4) ![Sample](./docs/microsoft-calendar-plugin.png)