add a potential implementation for refreshKeys

Signed-off-by: Kiss Miklos <miklos@roadie.io>
This commit is contained in:
Kiss Miklos
2022-06-20 11:56:20 +02:00
parent 5884220aec
commit def0eef285
11 changed files with 139 additions and 3 deletions
@@ -0,0 +1,41 @@
/*
* 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.
*/
exports.up = async function up(knex) {
await knex.schema.createTable('refresh_keys', table => {
table.comment(
'This table contains relations between entities and keys to trigger refreshes with',
);
table
.text('entity_ref')
.notNullable()
.comment('A reference to the entity that the refresh key is tied to');
table
.text('key')
.notNullable()
.comment(
'A reference to a key which should be used to trigger a refresh on this entity',
);
table.unique(['entity_ref', 'key']);
});
};
/**
* @param { import("knex").Knex } knex
*/
exports.down = async function down(knex) {
await knex.schema.dropTable('refresh_keys');
};
+9 -1
View File
@@ -169,9 +169,17 @@ export type CatalogProcessorErrorResult = {
location: LocationSpec;
};
/** @public */
export type CatalogProcessorRefreshKeysResult = {
type: 'refresh';
entity: Entity;
key: String;
};
/** @public */
export type CatalogProcessorResult =
| CatalogProcessorLocationResult
| CatalogProcessorEntityResult
| CatalogProcessorRelationResult
| CatalogProcessorErrorResult;
| CatalogProcessorErrorResult
| CatalogProcessorRefreshKeysResult;
@@ -33,12 +33,15 @@ import {
UpdateEntityCacheOptions,
ListParentsOptions,
ListParentsResult,
RefreshKeyOptions,
RefreshByKeyOptions,
} from './types';
import { DeferredEntity } from '../processing/types';
import { ProcessingIntervalFunction } from '../processing/refresh';
import { rethrowError, timestampToDateTime } from './conversion';
import { initDatabaseMetrics } from './metrics';
import {
DbRefreshKeysRow,
DbRefreshStateReferencesRow,
DbRefreshStateRow,
DbRelationsRow,
@@ -515,7 +518,40 @@ export class DefaultProcessingDatabase implements ProcessingDatabase {
throw new NotFoundError(`Failed to schedule ${entityRef} for refresh`);
}
}
async refreshByRefreshKey(
txOpaque: Transaction,
options: RefreshByKeyOptions,
) {
const tx = txOpaque as Knex.Transaction;
const { key } = options;
const rows = await tx<DbRefreshKeysRow>('refresh_keys')
.where({ key })
.select({
entity_ref: 'refresh_keys.entity_ref',
});
await Promise.all(rows.map(r => this.refresh(tx, r.entity_ref)));
}
async addRefreshKeys(
txOpaque: Transaction,
options: RefreshKeyOptions,
): Promise<void> {
const tx = txOpaque as Knex.Transaction;
const { keys } = options;
await Promise.all(
keys.map(k => {
return tx<DbRefreshKeysRow>('refresh_keys')
.insert({
entity_ref: stringifyEntityRef(k.entity),
key: k.key,
})
.onConflict(['entity_ref', 'key'])
.ignore();
}),
);
}
async transaction<T>(fn: (tx: Transaction) => Promise<T>): Promise<T> {
try {
let result: T | undefined = undefined;
@@ -43,6 +43,11 @@ export type DbRefreshStateRow = {
location_key?: string;
};
export type DbRefreshKeysRow = {
entity_ref: string;
key: string;
};
export type DbRefreshStateReferencesRow = {
source_key?: string;
source_entity_ref?: string;
@@ -81,6 +81,14 @@ export type ReplaceUnprocessedEntitiesOptions =
type: 'delta';
};
export type RefreshKeyOptions = {
keys: { key: String; entity: Entity }[];
};
export type RefreshByKeyOptions = {
key: string;
};
export type RefreshOptions = {
entityRef: string;
};
@@ -149,6 +157,11 @@ export interface ProcessingDatabase {
*/
refresh(txOpaque: Transaction, options: RefreshOptions): Promise<void>;
addRefreshKeys(
txOpaque: Transaction,
options: RefreshKeyOptions,
): Promise<void>;
/**
* Lists all ancestors of a given entityRef.
*
@@ -61,6 +61,13 @@ export class FileReaderProcessor implements CatalogProcessor {
},
})) {
emit(parseResult);
if (parseResult.type === 'entity') {
emit({
type: 'refresh',
key: path.normalize(fileMatch),
entity: parseResult.entity,
});
}
}
}
} else if (!optional) {
@@ -19,7 +19,11 @@ import { Entity } from '@backstage/catalog-model';
import { JsonValue } from '@backstage/types';
import { ScmIntegrationRegistry } from '@backstage/integration';
import yaml from 'yaml';
import { CatalogProcessor, LocationSpec } from '../../api';
import {
CatalogProcessor,
CatalogProcessorEmit,
LocationSpec,
} from '../../api';
/** @public */
export type PlaceholderResolverRead = (url: string) => Promise<Buffer>;
@@ -66,6 +70,7 @@ export class PlaceholderProcessor implements CatalogProcessor {
async preProcessEntity(
entity: Entity,
location: LocationSpec,
emit: CatalogProcessorEmit,
): Promise<Entity> {
const process = async (data: any): Promise<[any, boolean]> => {
if (!data || !(data instanceof Object)) {
@@ -102,6 +107,7 @@ export class PlaceholderProcessor implements CatalogProcessor {
const resolverKey = keys[0].substr(1);
const resolverValue = data[keys[0]];
emit({ type: 'refresh', key: resolverValue, entity });
const resolver = this.options.resolvers[resolverKey];
if (!resolver || typeof resolverValue !== 'string') {
// If there was no such placeholder resolver or if the value was not a
@@ -83,6 +83,13 @@ export class UrlReaderProcessor implements CatalogProcessor {
})) {
parseResults.push(parseResult);
emit(parseResult);
if (parseResult.type === 'entity') {
emit({
type: 'refresh',
key: item.url,
entity: parseResult.entity,
});
}
}
}
@@ -123,6 +123,12 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine {
let hashBuilder = this.createHash().update(errorsString);
if (result.ok) {
await this.processingDatabase.transaction(tx =>
this.processingDatabase.addRefreshKeys(tx, {
keys: result.refreshKeys,
}),
);
const { entityRefs: parents } =
await this.processingDatabase.transaction(tx =>
this.processingDatabase.listParents(tx, {
@@ -38,6 +38,10 @@ export class ProcessorOutputCollector {
private readonly errors = new Array<Error>();
private readonly relations = new Array<EntityRelationSpec>();
private readonly deferredEntities = new Array<DeferredEntity>();
private readonly refreshKeys = new Array<{
key: String;
entity: Entity;
}>();
private done = false;
constructor(
@@ -54,6 +58,7 @@ export class ProcessorOutputCollector {
return {
errors: this.errors,
relations: this.relations,
refreshKeys: this.refreshKeys,
deferredEntities: this.deferredEntities,
};
}
@@ -116,6 +121,8 @@ export class ProcessorOutputCollector {
this.relations.push(i.relation);
} else if (i.type === 'error') {
this.errors.push(i.error);
} else if (i.type === 'refresh') {
this.refreshKeys.push({ key: i.key, entity: i.entity });
}
}
}
@@ -26,7 +26,6 @@ export type EntityProcessingRequest = {
entity: Entity;
state?: JsonObject; // Versions for multiple deployments etc
};
/**
* The result of processing an entity.
* @public
@@ -38,6 +37,7 @@ export type EntityProcessingResult =
completedEntity: Entity;
deferredEntities: DeferredEntity[];
relations: EntityRelationSpec[];
refreshKeys: { key: String; entity: Entity }[];
errors: Error[];
}
| {