@backstage/backend-defaults
Version:
Backend defaults used by Backstage backend apps
150 lines (144 loc) • 5.27 kB
JavaScript
;
var errors = require('@backstage/errors');
var platformPath = require('path');
var ReadUrlResponseFactory = require('./ReadUrlResponseFactory.cjs.js');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
var platformPath__default = /*#__PURE__*/_interopDefaultCompat(platformPath);
const isInRange = (num, [start, end]) => {
return num >= start && num <= end;
};
const parsePortRange = (port) => {
const isRange = port.includes("-");
if (isRange) {
const range = port.split("-").map((v) => parseInt(v, 10)).filter(Boolean);
if (range.length !== 2) throw new Error(`Port range is not valid: ${port}`);
const [start, end] = range;
if (start <= 0 || end <= 0 || start > end)
throw new Error(`Port range is not valid: [${start}, ${end}]`);
return range;
}
const parsedPort = parseInt(port, 10);
return [parsedPort, parsedPort];
};
const parsePortPredicate = (port) => {
if (port) {
const range = parsePortRange(port);
return (url) => {
if (url.port) return isInRange(parseInt(url.port, 10), range);
if (url.protocol === "http:") return isInRange(80, range);
if (url.protocol === "https:") return isInRange(443, range);
return false;
};
}
return (url) => !url.port;
};
class FetchUrlReader {
/**
* The factory creates a single reader that will be used for reading any URL that's listed
* in configuration at `backend.reading.allow`. The allow list contains a list of objects describing
* targets to allow, containing the following fields:
*
* `host`:
* Either full hostnames to match, or subdomain wildcard matchers with a leading '*'.
* For example 'example.com' and '*.example.com' are valid values, 'prod.*.example.com' is not.
*
* `paths`:
* An optional list of paths which are allowed. If the list is omitted all paths are allowed.
*/
static factory = ({ config }) => {
const predicates = config.getOptionalConfigArray("backend.reading.allow")?.map((allowConfig) => {
const paths = allowConfig.getOptionalStringArray("paths");
const checkPath = paths ? (url) => {
const targetPath = platformPath__default.default.posix.normalize(url.pathname);
return paths.some(
(allowedPath) => targetPath.startsWith(allowedPath)
);
} : (_url) => true;
const host = allowConfig.getString("host");
const [hostname, port] = host.split(":");
const checkPort = parsePortPredicate(port);
if (hostname.startsWith("*.")) {
const suffix = hostname.slice(1);
return (url) => url.hostname.endsWith(suffix) && checkPath(url) && checkPort(url);
}
return (url) => url.hostname === hostname && checkPath(url) && checkPort(url);
}) ?? [];
const reader = new FetchUrlReader();
const predicate = (url) => predicates.some((p) => p(url));
return [{ reader, predicate }];
};
async read(url) {
const response = await this.readUrl(url);
return response.buffer();
}
async readUrl(url, options) {
let response;
try {
response = await fetch(url, {
headers: {
...options?.etag && { "If-None-Match": options.etag },
...options?.lastModifiedAfter && {
"If-Modified-Since": options.lastModifiedAfter.toUTCString()
},
...options?.token && { Authorization: `Bearer ${options.token}` }
},
// TODO(freben): The signal cast is there because pre-3.x versions of
// node-fetch have a very slightly deviating AbortSignal type signature.
// The difference does not affect us in practice however. The cast can
// be removed after we support ESM for CLI dependencies and migrate to
// version 3 of node-fetch.
// https://github.com/backstage/backstage/issues/8242
signal: options?.signal
});
} catch (e) {
throw new Error(`Unable to read ${url}, ${e}`);
}
if (response.status === 304) {
throw new errors.NotModifiedError();
}
if (response.ok) {
return ReadUrlResponseFactory.ReadUrlResponseFactory.fromResponse(response);
}
const message = `could not read ${url}, ${response.status} ${response.statusText}`;
if (response.status === 404) {
throw new errors.NotFoundError(message);
}
throw new Error(message);
}
async readTree() {
throw new Error("FetchUrlReader does not implement readTree");
}
async search(url, options) {
const { pathname } = new URL(url);
if (pathname.match(/[*?]/)) {
throw new Error("Unsupported search pattern URL");
}
try {
const data = await this.readUrl(url, options);
return {
files: [
{
url,
content: data.buffer,
lastModifiedAt: data.lastModifiedAt
}
],
etag: data.etag ?? ""
};
} catch (error) {
errors.assertError(error);
if (error.name === "NotFoundError") {
return {
files: [],
etag: ""
};
}
throw error;
}
}
toString() {
return "fetch{}";
}
}
exports.FetchUrlReader = FetchUrlReader;
//# sourceMappingURL=FetchUrlReader.cjs.js.map