UNPKG

@backstage/plugin-catalog-backend

Version:

The Backstage backend plugin that provides the Backstage catalog

159 lines (153 loc) 4.8 kB
'use strict'; 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