@backstage/plugin-catalog-backend
Version:
The Backstage backend plugin that provides the Backstage catalog
159 lines (153 loc) • 4.8 kB
JavaScript
var path = require('path');
var minimatch = require('minimatch');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
var path__default = /*#__PURE__*/_interopDefaultCompat(path);
class DefaultCatalogRulesEnforcer {
constructor(rules) {
this.rules = rules;
}
/**
* Default rules used by the catalog.
*
* Denies any location from specifying user or group entities.
*/
static defaultRules = [
{
allow: ["Component", "API", "Location"].map((kind) => ({ kind }))
}
];
/**
* Loads catalog rules from config.
*
* This reads `catalog.rules` and defaults to the default rules if no value is present.
* The value of the config should be a list of config objects, each with a single `allow`
* field which in turn is a list of entity kinds to allow.
*
* If there is no matching rule to allow an ingested entity, it will be rejected by the catalog.
*
* It also reads in rules from `catalog.locations`, where each location can have a list
* of rules for that specific location, specified in a `rules` field.
*
* For example:
*
* ```yaml
* catalog:
* rules:
* - allow: [Component, API]
* - allow: [Template]
* locations:
* - type: url
* pattern: https://github.com/org/*\/blob/master/template.yaml
* - allow: [Location]
* locations:
* - type: url
* pattern: https://github.com/org/repo/blob/master/location.yaml
*
* locations:
* - type: url
* target: https://github.com/org/repo/blob/master/users.yaml
* rules:
* - allow: [User, Group]
* - type: url
* target: https://github.com/org/repo/blob/master/systems.yaml
* rules:
* - allow: [System]
* ```
*/
static fromConfig(config) {
const rules = new Array();
if (config.has("catalog.rules")) {
const globalRules = config.getConfigArray("catalog.rules").map((ruleConf) => ({
allow: ruleConf.getStringArray("allow").map((kind) => ({ kind })),
locations: ruleConf.getOptionalConfigArray("locations")?.map((locationConfig) => {
const location = {
pattern: locationConfig.getOptionalString("pattern"),
type: locationConfig.getString("type"),
exact: locationConfig.getOptionalString("exact")
};
if (location.pattern && location.exact) {
throw new Error(
"A catalog rule location cannot have both exact and pattern values"
);
}
return location;
})
}));
rules.push(...globalRules);
} else {
rules.push(...DefaultCatalogRulesEnforcer.defaultRules);
}
if (config.has("catalog.locations")) {
const locationRules = config.getConfigArray("catalog.locations").flatMap((locConf) => {
if (!locConf.has("rules")) {
return [];
}
const type = locConf.getString("type");
const exact = resolveTarget(type, locConf.getString("target"));
return locConf.getConfigArray("rules").map((ruleConf) => ({
allow: ruleConf.getStringArray("allow").map((kind) => ({ kind })),
locations: [{ type, exact }]
}));
});
rules.push(...locationRules);
}
return new DefaultCatalogRulesEnforcer(rules);
}
/**
* Checks whether a specific entity/location combination is allowed
* according to the configured rules.
*/
isAllowed(entity, location) {
for (const rule of this.rules) {
if (!this.matchLocation(location, rule.locations)) {
continue;
}
if (this.matchEntity(entity, rule.allow)) {
return true;
}
}
return false;
}
matchLocation(location, matchers) {
if (!matchers) {
return true;
}
for (const matcher of matchers) {
if (matcher.type !== location?.type) {
continue;
}
if (matcher.exact && matcher.exact !== location?.target) {
continue;
}
if (matcher.pattern && !minimatch.minimatch(location?.target, matcher.pattern, {
nocase: true,
dot: true
})) {
continue;
}
return true;
}
return false;
}
matchEntity(entity, matchers) {
if (!matchers) {
return true;
}
for (const matcher of matchers) {
if (entity?.kind?.toLowerCase() !== matcher.kind.toLowerCase()) {
continue;
}
return true;
}
return false;
}
}
function resolveTarget(type, target) {
if (type !== "file") {
return target;
}
return path__default.default.resolve(target);
}
exports.DefaultCatalogRulesEnforcer = DefaultCatalogRulesEnforcer;
//# sourceMappingURL=CatalogRules.cjs.js.map
;