@backstage/backend-defaults
Version:
Backend defaults used by Backstage backend apps
303 lines (299 loc) • 9.91 kB
JavaScript
;
var integrationAwsNode = require('@backstage/integration-aws-node');
var integration = require('@backstage/integration');
var errors = require('@backstage/errors');
var credentialProviders = require('@aws-sdk/credential-providers');
var clientCodecommit = require('@aws-sdk/client-codecommit');
var stream = require('stream');
var ReadUrlResponseFactory = require('./ReadUrlResponseFactory.cjs.js');
var posix = require('path/posix');
var abortController = require('@aws-sdk/abort-controller');
function parseUrl(url, requireGitPath = false) {
const parsedUrl = new URL(url);
if (parsedUrl.pathname.includes("/files/edit/")) {
throw new Error(
"Please provide the view url to yaml file from CodeCommit, not the edit url"
);
}
if (requireGitPath && !parsedUrl.pathname.includes("/browse/")) {
throw new Error("Please provide full path to yaml file from CodeCommit");
}
const hostMatch = parsedUrl.host.match(
/^([^\.]+)\.console\.aws\.amazon\.com$/
);
if (!hostMatch) {
throw new Error(
`Invalid AWS CodeCommit URL (unexpected host format): ${url}`
);
}
const [, region] = hostMatch;
const pathMatch = parsedUrl.pathname.match(
/^\/codesuite\/codecommit\/repositories\/([^\/]+)\/browse\/((.*)\/)?--\/(.*)$/
);
if (!pathMatch) {
if (!requireGitPath) {
const pathname = parsedUrl.pathname.split("/--/")[0].replace("/codesuite/codecommit/repositories/", "");
const [repositoryName2, commitSpecifier2] = pathname.split("/browse");
return {
region,
repositoryName: repositoryName2.replace(/^\/|\/$/g, ""),
path: "/",
commitSpecifier: commitSpecifier2 === "" ? void 0 : commitSpecifier2?.replace(/^\/|\/$/g, "")
};
}
throw new Error(
`Invalid AWS CodeCommit URL (unexpected path format): ${url}`
);
}
const [, repositoryName, , commitSpecifier, path] = pathMatch;
return {
region,
repositoryName,
path,
// the commitSpecifier is passed to AWS SDK which does not allow empty strings so replace empty string with undefined
commitSpecifier: commitSpecifier === "" ? void 0 : commitSpecifier
};
}
class AwsCodeCommitUrlReader {
static factory = ({ config, treeResponseFactory }) => {
const integrations = integration.ScmIntegrations.fromConfig(config);
const credsManager = integrationAwsNode.DefaultAwsCredentialsManager.fromConfig(config);
return integrations.awsCodeCommit.list().map((integration) => {
const reader = new AwsCodeCommitUrlReader(credsManager, integration, {
treeResponseFactory
});
const predicate = (url) => {
return url.host.endsWith(integration.config.host) && url.pathname.startsWith("/codesuite/codecommit");
};
return { reader, predicate };
});
};
credsManager;
integration;
deps;
constructor(credsManager, integration, deps) {
this.credsManager = credsManager;
this.integration = integration;
this.deps = deps;
}
/**
* If accessKeyId and secretAccessKey are missing, the standard credentials provider chain will be used:
* https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/auth/DefaultAWSCredentialsProviderChain.html
*/
static buildStaticCredentials(accessKeyId, secretAccessKey) {
return async () => {
return {
accessKeyId,
secretAccessKey
};
};
}
static async buildCredentials(credsManager, region, integration) {
if (!integration) {
return (await credsManager.getCredentialProvider()).sdkCredentialProvider;
}
const accessKeyId = integration.config.accessKeyId;
const secretAccessKey = integration.config.secretAccessKey;
let explicitCredentials;
if (accessKeyId && secretAccessKey) {
explicitCredentials = AwsCodeCommitUrlReader.buildStaticCredentials(
accessKeyId,
secretAccessKey
);
} else {
explicitCredentials = (await credsManager.getCredentialProvider()).sdkCredentialProvider;
}
const roleArn = integration.config.roleArn;
if (roleArn) {
return credentialProviders.fromTemporaryCredentials({
masterCredentials: explicitCredentials,
params: {
RoleSessionName: "backstage-aws-code-commit-url-reader",
RoleArn: roleArn,
ExternalId: integration.config.externalId
},
clientConfig: { region }
});
}
return explicitCredentials;
}
async buildCodeCommitClient(credsManager, region, integration) {
const credentials = await AwsCodeCommitUrlReader.buildCredentials(
credsManager,
region,
integration
);
const codeCommit = new clientCodecommit.CodeCommitClient({
customUserAgent: "backstage-aws-codecommit-url-reader",
region,
credentials
});
return codeCommit;
}
async readUrl(url, options) {
try {
const { path, repositoryName, region, commitSpecifier } = parseUrl(
url,
true
);
const codeCommitClient = await this.buildCodeCommitClient(
this.credsManager,
region,
this.integration
);
const abortController$1 = new abortController.AbortController();
const input = {
repositoryName,
commitSpecifier,
filePath: path
};
options?.signal?.addEventListener("abort", () => abortController$1.abort());
const getObjectCommand = new clientCodecommit.GetFileCommand(input);
const response = await codeCommitClient.send(
getObjectCommand,
{
abortSignal: abortController$1.signal
}
);
if (options?.etag && options.etag === response.commitId) {
throw new errors.NotModifiedError();
}
return ReadUrlResponseFactory.ReadUrlResponseFactory.fromReadable(
stream.Readable.from([response?.fileContent]),
{
etag: response.commitId
}
);
} catch (e) {
if (e.$metadata && e.$metadata.httpStatusCode === 304) {
throw new errors.NotModifiedError();
}
if (e.name && e.name === "NotModifiedError") {
throw new errors.NotModifiedError();
}
throw new errors.ForwardedError("Could not retrieve file from CodeCommit", e);
}
}
async readTreePath(codeCommitClient, abortSignal, path, repositoryName, commitSpecifier, etag) {
const getFolderCommand = new clientCodecommit.GetFolderCommand({
folderPath: path,
repositoryName,
commitSpecifier
});
const response = await codeCommitClient.send(getFolderCommand, {
abortSignal
});
if (etag && etag === response.commitId) {
throw new errors.NotModifiedError();
}
const output = [];
if (response.files) {
response.files.forEach((file) => {
if (file.absolutePath) {
output.push(file.absolutePath);
}
});
}
if (!response.subFolders) {
return output;
}
for (const subFolder of response.subFolders) {
if (subFolder.absolutePath) {
output.push(
...await this.readTreePath(
codeCommitClient,
abortSignal,
subFolder.absolutePath,
repositoryName,
commitSpecifier,
etag
)
);
}
}
return output;
}
async readTree(url, options) {
try {
const { path, repositoryName, region, commitSpecifier } = parseUrl(url);
const codeCommitClient = await this.buildCodeCommitClient(
this.credsManager,
region,
this.integration
);
const abortController$1 = new abortController.AbortController();
options?.signal?.addEventListener("abort", () => abortController$1.abort());
const allFiles = await this.readTreePath(
codeCommitClient,
abortController$1.signal,
path,
repositoryName,
commitSpecifier,
options?.etag
);
const responses = [];
for (let i = 0; i < allFiles.length; i++) {
const getFileCommand = new clientCodecommit.GetFileCommand({
repositoryName,
filePath: String(allFiles[i]),
commitSpecifier
});
const response = await codeCommitClient.send(getFileCommand);
const objectData = await stream.Readable.from([response?.fileContent]);
responses.push({
data: objectData,
path: posix.relative(
path.startsWith("/") ? path : `/${path}`,
allFiles[i].startsWith("/") ? allFiles[i] : `/${allFiles[i]}`
)
});
}
return await this.deps.treeResponseFactory.fromReadableArray(responses);
} catch (e) {
if (e.name && e.name === "NotModifiedError") {
throw new errors.NotModifiedError();
}
throw new errors.ForwardedError(
"Could not retrieve file tree from CodeCommit",
e
);
}
}
async search(url, options) {
const { path } = parseUrl(url, true);
if (path.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() {
const secretAccessKey = this.integration.config.secretAccessKey;
return `awsCodeCommit{host=${this.integration.config.host},authed=${Boolean(
secretAccessKey
)}}`;
}
}
exports.AwsCodeCommitUrlReader = AwsCodeCommitUrlReader;
exports.parseUrl = parseUrl;
//# sourceMappingURL=AwsCodeCommitUrlReader.cjs.js.map