UNPKG

@backstage/backend-defaults

Version:

Backend defaults used by Backstage backend apps

303 lines (299 loc) • 9.91 kB
'use strict'; 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