@secustor/backstage-plugin-renovate-backend
Version:
256 lines (250 loc) • 9.11 kB
JavaScript
;
var backendPluginApi = require('@backstage/backend-plugin-api');
var types = require('./types.cjs.js');
var is = require('@sindresorhus/is');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
var is__default = /*#__PURE__*/_interopDefaultCompat(is);
const migrationsDir = backendPluginApi.resolvePackagePath(
"@secustor/backstage-plugin-renovate-backend",
"migrations"
);
class DatabaseHandler {
constructor(client, logger) {
this.client = client;
this.logger = logger;
}
static async create(options) {
const { database, logger } = options;
const client = await database.getClient();
if (!database.migrations?.skip) {
await client.migrate.latest({
directory: migrationsDir
});
}
return new DatabaseHandler(client, logger);
}
async addReport(options) {
const { runID, taskID, report, target } = options;
const logger = options.logger ?? this.logger;
const timestamp = /* @__PURE__ */ new Date();
const inserts = [];
for (const [repository, value] of Object.entries(report.repositories)) {
inserts.push({
run_id: runID,
task_id: taskID,
timestamp,
host: target.host,
repository,
report: value
});
}
await this.client("reports").insert(inserts).catch((reason) => logger.error("Failed insert data", reason));
await this.updateDependencies(timestamp, options);
}
async getReports(query) {
const builder = this.client.select();
if (query) {
builder.where(query);
}
const rows = await builder.from("reports");
return rows.map((row) => {
return {
runID: row.run_id,
taskID: row.task_id,
timestamp: row.timestamp,
host: row.host,
repository: row.repository,
// if the JSON field has not been auto-parsed do it manually
report: is__default.default.string(row.report) ? JSON.parse(row.report) : row.report
};
});
}
async getTargets(table = "reports") {
return this.client.select().distinct("host", "repository").from(table);
}
async deleteReportsByTarget({ host, repository }, options) {
const offset = getOffset(options);
const toBeDeletedIDs = this.client("reports").select("run_id").where("host", host).andWhere("repository", repository).orderBy("timestamp", "DESC").offset(offset);
return this.client("reports").delete().whereIn("run_id", [toBeDeletedIDs]);
}
async deleteReports(options) {
const targets = await this.getTargets();
const modified = await Promise.all(
targets.map((target) => this.deleteReportsByTarget(target, options))
);
return modified.reduce((a, b) => a + b, 0);
}
async updateDependencies(timestamp, options) {
const { runID, report, target } = options;
const dependencies = [];
for (const [repository, repositoryContent] of Object.entries(
report.repositories
)) {
for (const [manager, packageFiles] of Object.entries(
repositoryContent.packageFiles
)) {
for (const packageFile of packageFiles) {
const packageFilePath = packageFile.packageFile;
for (const dependency of packageFile.deps) {
const {
packageName,
depName,
depType,
datasource,
currentValue,
currentVersion,
skipReason,
registryUrl,
sourceUrl,
currentVersionTimestamp
} = dependency;
const massagedDepName = depName ?? packageName;
if (!massagedDepName) {
continue;
}
dependencies.push({
run_id: runID,
host: target.host,
extractionTimestamp: timestamp,
repository,
manager,
datasource: datasource ?? packageFile.datasource ?? "no-datasource",
depName: massagedDepName,
packageName,
packageFile: packageFilePath,
depType,
currentValue,
currentVersion,
currentVersionTimestamp,
skipReason,
registryUrl,
sourceUrl
});
}
}
}
}
await this.client("dependencies").insert(dependencies);
}
async getDependencies(filters, pagination) {
const page = pagination?.page ?? 0;
const pageSize = pagination?.pageSize ?? 500;
const builder = this.client("dependencies").select();
this.applyDependencyFilters(builder, filters);
const total = await this.getDependenciesCount(filters);
const offset = page * pageSize;
return {
result: await builder.offset(offset).limit(pageSize),
total,
page,
pageSize,
pageCount: Math.ceil(total / pageSize)
};
}
async getDependenciesCount(filters) {
const builder = this.client("dependencies").count({ count: "*" });
this.applyDependencyFilters(builder, filters);
const count = await builder.first().then((result) => result?.count);
if (is__default.default.string(count)) {
return Number.parseInt(count, 10);
}
return count ?? 0;
}
applyDependencyFilters(builder, filters) {
if (filters.host) {
builder.whereIn("host", filters.host);
}
if (filters.repository) {
builder.whereIn("repository", filters.repository);
}
if (filters.manager) {
builder.whereIn("manager", filters.manager);
}
if (filters.datasource) {
builder.whereIn("datasource", filters.datasource);
}
if (filters.depName) {
builder.whereIn("depName", filters.depName);
}
if (filters.depType) {
builder.whereIn("depType", filters.depType);
}
if (filters.latestOnly) {
const runIDs = this.client("dependencies").select("d.run_id").from("dependencies as d").join(
this.client("dependencies").select("host", "repository").max("extractionTimestamp as max_timestamp").groupBy("host", "repository").as("max_d"),
// eslint-disable-next-line func-names
function() {
this.on("d.host", "=", "max_d.host").andOn("d.repository", "=", "max_d.repository").andOn("d.extractionTimestamp", "=", "max_d.max_timestamp");
}
).distinct();
builder.whereIn("run_id", runIDs);
}
}
/**
* Gets the available values for the dependencies stored in the database.
* If filters are supplied, OTHER values are filtered accordingly, if a filter is supplied, all values are returned
* @param filters
*/
async getDependenciesValues(filters) {
const baseBuilder = this.client(
"dependencies"
);
const limitedValuesBuilder = baseBuilder.clone();
const allValuesKeys = [];
const limitedValuesKeys = [];
for (const filterKey of types.DependencyValueFiltersKeys) {
const suppliedFilter = filters?.[filterKey];
if (suppliedFilter) {
limitedValuesBuilder.whereIn(filterKey, suppliedFilter);
limitedValuesKeys.push(filterKey);
continue;
}
allValuesKeys.push(filterKey);
}
const result = {
datasource: [],
manager: [],
depType: [],
depName: [],
host: [],
packageFile: [],
repository: []
};
const allValues = allValuesKeys.map(
async (filterKey) => {
const values = await limitedValuesBuilder.clone().select(filterKey).distinct().pluck(filterKey);
result[filterKey] = values.filter(is__default.default.string);
}
);
const limitedValues = limitedValuesKeys.map(async (filterKey) => {
const values = await baseBuilder.clone().select(filterKey).distinct().pluck(filterKey);
result[filterKey] = values.filter(is__default.default.string);
});
await Promise.all([...allValues, ...limitedValues]);
return result;
}
async deleteDependencies(options) {
const targets = await this.getTargets("dependencies");
const modified = await Promise.all(
targets.map((target) => this.deleteDependenciesByTarget(target, options))
);
return modified.reduce((a, b) => a + b, 0);
}
async deleteDependenciesByTarget({ host, repository }, options) {
const offset = getOffset(options);
const dependencies = this.client("dependencies").select("run_id", "extractionTimestamp").distinct("host", "repository").where("host", host).andWhere("repository", repository).as("runs");
const toBeDeletedIDs = this.client(dependencies).select("run_id").orderBy("extractionTimestamp", "DESC").offset(offset);
return this.client("dependencies").delete().whereIn("run_id", [toBeDeletedIDs]);
}
}
function getOffset(options) {
let offset = 0;
if (is__default.default.nullOrUndefined(options?.keepLatest) || is__default.default.boolean(options?.keepLatest)) {
offset = options?.keepLatest ? 1 : 0;
} else {
offset = options.keepLatest;
}
return offset;
}
exports.DatabaseHandler = DatabaseHandler;
//# sourceMappingURL=databaseHandler.cjs.js.map