@backstage/backend-defaults
Version:
Backend defaults used by Backstage backend apps
225 lines (219 loc) • 8.48 kB
JavaScript
;
var base64Stream = require('base64-stream');
var fetch = require('node-fetch');
var stream = require('stream');
var integration = require('@backstage/integration');
var errors = require('@backstage/errors');
var minimatch = require('minimatch');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
var fetch__default = /*#__PURE__*/_interopDefaultCompat(fetch);
class GerritUrlReader {
static factory = ({ config, treeResponseFactory }) => {
const integrations = integration.ScmIntegrations.fromConfig(config);
if (!integrations.gerrit) {
return [];
}
return integrations.gerrit.list().map((integration) => {
const reader = new GerritUrlReader(integration, { treeResponseFactory });
const predicate = (url) => {
const gitilesUrl = new URL(integration.config.gitilesBaseUrl);
return url.host === gitilesUrl.host;
};
return { reader, predicate };
});
};
integration;
deps;
constructor(integration, deps) {
this.integration = integration;
this.deps = deps;
}
async read(url) {
const response = await this.readUrl(url);
return response.buffer();
}
async readUrl(url, options) {
const apiUrl = integration.getGerritFileContentsApiUrl(this.integration.config, url);
let response;
try {
response = await fetch__default.default(apiUrl, {
method: "GET",
...integration.getGerritRequestOptions(this.integration.config),
// 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 gerrit file ${url}, ${e}`);
}
if (response.ok) {
let responseBody;
return {
buffer: async () => {
if (responseBody === void 0) {
responseBody = await response.text();
}
return Buffer.from(responseBody, "base64");
},
stream: () => {
const readable = stream.Readable.from(response.body);
return readable.pipe(new base64Stream.Base64Decode());
}
};
}
if (response.status === 404) {
throw new errors.NotFoundError(`File ${url} not found.`);
}
throw new Error(
`${url} could not be read as ${apiUrl}, ${response.status} ${response.statusText}`
);
}
async readTree(url, options) {
const urlRevision = await this.getRevisionForUrl(url, options);
return this.readTreeFromGitiles(url, urlRevision, options);
}
async search(url, options) {
const { path } = integration.parseGitilesUrlRef(this.integration.config, url);
if (!path.match(/[*?]/)) {
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;
}
}
const urlRevision = await this.getRevisionForUrl(url, options);
const files = await this.searchFilesFromGitiles(url, options);
return { files, etag: urlRevision };
}
toString() {
const { host, password } = this.integration.config;
return `gerrit{host=${host},authed=${Boolean(password)}}`;
}
async readTreeFromGitiles(url, revision, options) {
const archiveUrl = integration.buildGerritGitilesArchiveUrlFromLocation(
this.integration.config,
url
);
const archiveResponse = await fetch__default.default(archiveUrl, {
...integration.getGerritRequestOptions(this.integration.config),
// 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
});
if (archiveResponse.status === 404) {
throw new errors.NotFoundError(`Not found: ${archiveUrl}`);
}
if (!archiveResponse.ok) {
throw new Error(
`${url} could not be read as ${archiveUrl}, ${archiveResponse.status} ${archiveResponse.statusText}`
);
}
return await this.deps.treeResponseFactory.fromTarArchive({
stream: archiveResponse.body,
etag: revision,
filter: options?.filter,
stripFirstDirectory: false
});
}
async getRevisionForUrl(url, options) {
const { ref, refType } = integration.parseGitilesUrlRef(this.integration.config, url);
if (refType === "sha") {
if (options?.etag === ref) {
throw new errors.NotModifiedError();
}
return ref;
}
const apiUrl = integration.getGerritBranchApiUrl(this.integration.config, url);
let response;
try {
response = await fetch__default.default(apiUrl, {
method: "GET",
...integration.getGerritRequestOptions(this.integration.config)
});
} catch (e) {
throw new Error(`Unable to read branch state ${url}, ${e}`);
}
if (!response.ok) {
throw await errors.ResponseError.fromResponse(response);
}
const branchInfo = await integration.parseGerritJsonResponse(response);
if (options?.etag === branchInfo.revision) {
throw new errors.NotModifiedError();
}
return branchInfo.revision;
}
async searchFilesFromGitiles(url, options) {
const { path, basePath } = integration.parseGitilesUrlRef(this.integration.config, url);
const treeUrl = `${basePath}/?format=JSON&recursive`.replace(
this.integration.config.gitilesBaseUrl,
integration.getGitilesAuthenticationUrl(this.integration.config)
);
const treeResponse = await fetch__default.default(treeUrl, {
...integration.getGerritRequestOptions(this.integration.config),
// 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
});
if (!treeResponse.ok) {
throw await errors.ResponseError.fromResponse(treeResponse);
}
const res = await integration.parseGerritJsonResponse(treeResponse);
const matcher = new minimatch.Minimatch(decodeURIComponent(path).replace(/^\/+/, ""));
const matching = res.entries.filter(
(item) => item.type === "blob" && item.name && matcher.match(item.name)
);
return matching.map((item) => ({
url: `${basePath}/${item.name}`,
content: async () => {
const apiUrl = integration.getGerritFileContentsApiUrl(
this.integration.config,
`${basePath}/${item.name}`
);
const response = await fetch__default.default(apiUrl, {
method: "GET",
...integration.getGerritRequestOptions(this.integration.config),
// 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
});
const responseBody = await response.text();
return Buffer.from(responseBody, "base64");
}
}));
}
}
exports.GerritUrlReader = GerritUrlReader;
//# sourceMappingURL=GerritUrlReader.cjs.js.map