@backstage/plugin-techdocs-node 1.13.11-next.0 → 1.14.1-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/dist/helpers.cjs.js +1 -1
- package/dist/helpers.cjs.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/stages/generate/DockerContainerRunner.cjs.js +4 -4
- package/dist/stages/generate/DockerContainerRunner.cjs.js.map +1 -1
- package/dist/stages/generate/helpers.cjs.js +5 -5
- package/dist/stages/generate/helpers.cjs.js.map +1 -1
- package/dist/stages/generate/index.cjs.js +1 -1
- package/dist/stages/generate/techdocs.cjs.js +4 -2
- package/dist/stages/generate/techdocs.cjs.js.map +1 -1
- package/dist/stages/publish/awsS3.cjs.js +59 -9
- package/dist/stages/publish/awsS3.cjs.js.map +1 -1
- package/dist/stages/publish/azureBlobStorage.cjs.js +1 -1
- package/dist/stages/publish/azureBlobStorage.cjs.js.map +1 -1
- package/dist/stages/publish/googleStorage.cjs.js +1 -1
- package/dist/stages/publish/googleStorage.cjs.js.map +1 -1
- package/dist/stages/publish/helpers.cjs.js +1 -1
- package/dist/stages/publish/helpers.cjs.js.map +1 -1
- package/dist/stages/publish/local.cjs.js +2 -2
- package/dist/stages/publish/local.cjs.js.map +1 -1
- package/dist/stages/publish/migrations/GoogleMigration.cjs.js +2 -2
- package/dist/stages/publish/migrations/GoogleMigration.cjs.js.map +1 -1
- package/dist/stages/publish/openStackSwift.cjs.js +6 -6
- package/dist/stages/publish/openStackSwift.cjs.js.map +1 -1
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"awsS3.cjs.js","sources":["../../../src/stages/publish/awsS3.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Entity, CompoundEntityRef } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { assertError, ForwardedError } from '@backstage/errors';\n\n// Maximum size in bytes for a single upload part (5MB)\nconst MAX_SINGLE_UPLOAD_BYTES = 5 * 1024 * 1024;\n\nimport {\n AwsCredentialsManager,\n DefaultAwsCredentialsManager,\n} from '@backstage/integration-aws-node';\nimport {\n GetObjectCommand,\n CopyObjectCommand,\n DeleteObjectCommand,\n HeadBucketCommand,\n HeadObjectCommand,\n PutObjectCommand,\n PutObjectCommandInput,\n ListObjectsV2CommandOutput,\n ListObjectsV2Command,\n S3Client,\n S3ServiceException,\n} from '@aws-sdk/client-s3';\nimport { fromTemporaryCredentials } from '@aws-sdk/credential-providers';\nimport { NodeHttpHandler } from '@smithy/node-http-handler';\nimport { Upload } from '@aws-sdk/lib-storage';\nimport { AwsCredentialIdentityProvider } from '@aws-sdk/types';\nimport { HttpsProxyAgent } from 'hpagent';\nimport express from 'express';\nimport fs from 'fs-extra';\nimport JSON5 from 'json5';\nimport createLimiter from 'p-limit';\nimport path from 'path';\nimport { Readable } from 'stream';\nimport {\n bulkStorageOperation,\n getCloudPathForLocalPath,\n getFileTreeRecursively,\n getHeadersForFileExtension,\n getStaleFiles,\n isValidContentPath,\n lowerCaseEntityTriplet,\n lowerCaseEntityTripletInStoragePath,\n normalizeExternalStorageRootPath,\n} from './helpers';\nimport {\n PublisherBase,\n PublishRequest,\n PublishResponse,\n ReadinessResponse,\n TechDocsMetadata,\n} from './types';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\nconst streamToBuffer = (stream: Readable): Promise<Buffer> => {\n return new Promise((resolve, reject) => {\n try {\n const chunks: any[] = [];\n stream.on('data', chunk => chunks.push(chunk));\n stream.on('error', (e: Error) =>\n reject(new ForwardedError('Unable to read stream', e)),\n );\n stream.on('end', () => resolve(Buffer.concat(chunks)));\n } catch (e) {\n throw new ForwardedError('Unable to parse the response data', e);\n }\n });\n};\n\nexport class AwsS3Publish implements PublisherBase {\n public readonly storageClient: S3Client;\n private readonly bucketName: string;\n private readonly legacyPathCasing: boolean;\n private readonly logger: LoggerService;\n private readonly bucketRootPath: string;\n private readonly sse?: 'aws:kms' | 'AES256';\n private readonly maxAttempts: number;\n\n constructor(options: {\n storageClient: S3Client;\n bucketName: string;\n legacyPathCasing: boolean;\n logger: LoggerService;\n bucketRootPath: string;\n sse?: 'aws:kms' | 'AES256';\n maxAttempts: number;\n }) {\n this.storageClient = options.storageClient;\n this.bucketName = options.bucketName;\n this.legacyPathCasing = options.legacyPathCasing;\n this.logger = options.logger;\n this.bucketRootPath = options.bucketRootPath;\n this.sse = options.sse;\n this.maxAttempts = options.maxAttempts;\n }\n\n static async fromConfig(\n config: Config,\n logger: LoggerService,\n ): Promise<PublisherBase> {\n let bucketName = '';\n try {\n bucketName = config.getString('techdocs.publisher.awsS3.bucketName');\n } catch (error) {\n throw new Error(\n \"Since techdocs.publisher.type is set to 'awsS3' in your app config, \" +\n 'techdocs.publisher.awsS3.bucketName is required.',\n );\n }\n\n const bucketRootPath = normalizeExternalStorageRootPath(\n config.getOptionalString('techdocs.publisher.awsS3.bucketRootPath') || '',\n );\n\n const sse = config.getOptionalString('techdocs.publisher.awsS3.sse') as\n | 'aws:kms'\n | 'AES256'\n | undefined;\n\n // AWS Region is an optional config. If missing, default AWS env variable AWS_REGION\n // or AWS shared credentials file at ~/.aws/credentials will be used.\n const region = config.getOptionalString('techdocs.publisher.awsS3.region');\n\n // Credentials can optionally be configured by specifying the AWS account ID, which will retrieve credentials\n // for the account from the 'aws' section of the app config.\n // Credentials can also optionally be directly configured in the techdocs awsS3 config, but this method is\n // deprecated.\n // If no credentials are configured, the AWS SDK V3's default credential chain will be used.\n const accountId = config.getOptionalString(\n 'techdocs.publisher.awsS3.accountId',\n );\n const credentialsConfig = config.getOptionalConfig(\n 'techdocs.publisher.awsS3.credentials',\n );\n const credsManager = DefaultAwsCredentialsManager.fromConfig(config);\n const sdkCredentialProvider = await AwsS3Publish.buildCredentials(\n credsManager,\n accountId,\n credentialsConfig,\n region,\n );\n\n // AWS endpoint is an optional config. If missing, the default endpoint is built from\n // the configured region.\n const endpoint = config.getOptionalString(\n 'techdocs.publisher.awsS3.endpoint',\n );\n\n // AWS HTTPS proxy is an optional config. If missing, no proxy is used\n const httpsProxy = config.getOptionalString(\n 'techdocs.publisher.awsS3.httpsProxy',\n );\n\n // AWS forcePathStyle is an optional config. If missing, it defaults to false. Needs to be enabled for cases\n // where endpoint url points to locally hosted S3 compatible storage like Localstack\n const forcePathStyle = config.getOptionalBoolean(\n 'techdocs.publisher.awsS3.s3ForcePathStyle',\n );\n\n // AWS MAX ATTEMPTS is an optional config. If missing, default value of 5 is used\n const maxAttempts = config.getOptionalNumber(\n 'techdocs.publisher.awsS3.maxAttempts',\n );\n\n const storageClient = new S3Client({\n customUserAgent: 'backstage-aws-techdocs-s3-publisher',\n credentialDefaultProvider: () => sdkCredentialProvider,\n ...(region && { region }),\n ...(endpoint && { endpoint }),\n ...(forcePathStyle && { forcePathStyle }),\n // Enhanced retry configuration for better reliability\n maxAttempts: maxAttempts || 5,\n retryMode: 'adaptive',\n // Enhanced connection settings for large file uploads\n requestHandler: new NodeHttpHandler({\n ...(httpsProxy && {\n httpsAgent: new HttpsProxyAgent({ proxy: httpsProxy }),\n }),\n connectionTimeout: 60000,\n socketTimeout: 120000,\n }),\n });\n\n const legacyPathCasing =\n config.getOptionalBoolean(\n 'techdocs.legacyUseCaseSensitiveTripletPaths',\n ) || false;\n\n return new AwsS3Publish({\n storageClient,\n bucketName,\n bucketRootPath,\n legacyPathCasing,\n logger,\n sse,\n maxAttempts: maxAttempts || 5,\n });\n }\n\n private static buildStaticCredentials(\n accessKeyId: string,\n secretAccessKey: string,\n ): AwsCredentialIdentityProvider {\n return async () => {\n return Promise.resolve({\n accessKeyId,\n secretAccessKey,\n });\n };\n }\n\n private static async buildCredentials(\n credsManager: AwsCredentialsManager,\n accountId?: string,\n config?: Config,\n region?: string,\n ): Promise<AwsCredentialIdentityProvider> {\n // Pull credentials for the specified account ID from the 'aws' config section\n if (accountId) {\n return (await credsManager.getCredentialProvider({ accountId }))\n .sdkCredentialProvider;\n }\n\n // Fall back to the default credential chain if neither account ID\n // nor explicit credentials are provided\n if (!config) {\n return (await credsManager.getCredentialProvider()).sdkCredentialProvider;\n }\n\n // Pull credentials from the techdocs config section (deprecated)\n const accessKeyId = config.getOptionalString('accessKeyId');\n const secretAccessKey = config.getOptionalString('secretAccessKey');\n const explicitCredentials: AwsCredentialIdentityProvider =\n accessKeyId && secretAccessKey\n ? AwsS3Publish.buildStaticCredentials(accessKeyId, secretAccessKey)\n : (await credsManager.getCredentialProvider()).sdkCredentialProvider;\n\n const roleArn = config.getOptionalString('roleArn');\n if (roleArn) {\n return fromTemporaryCredentials({\n masterCredentials: explicitCredentials,\n params: {\n RoleSessionName: 'backstage-aws-techdocs-s3-publisher',\n RoleArn: roleArn,\n },\n clientConfig: { region },\n });\n }\n\n return explicitCredentials;\n }\n /**\n * Custom retry wrapper for S3 operations with detailed error handling.\n */\n public async retryOperation<TOutput>(\n operation: () => Promise<TOutput>,\n operationName: string,\n maxAttempts: number = 3,\n shouldRetry: (\n error: S3ServiceException,\n ) => boolean = this.defaultShouldRetry.bind(this),\n ): Promise<TOutput> {\n for (let attempt = 1; attempt < maxAttempts; attempt++) {\n try {\n return await operation();\n } catch (error) {\n const e = error as S3ServiceException;\n if (!shouldRetry(e)) {\n this.logger.error(`${operationName} failed: ${e.message}`);\n throw e;\n }\n\n this.logger.warn(`${operationName} failed, retrying...`, {\n attempt,\n maxAttempts,\n error: e.message,\n errorCode: e.name,\n httpStatusCode: e.$metadata?.httpStatusCode,\n });\n\n // Enhanced exponential backoff with jitter\n const baseDelay = operationName.startsWith('Upload-') ? 2000 : 1000;\n const backoffDelay = Math.min(\n baseDelay * Math.pow(2, attempt - 1),\n 30000,\n );\n const jitter = Math.random() * 1000;\n await new Promise(resolve =>\n setTimeout(resolve, backoffDelay + jitter),\n );\n }\n }\n return await operation();\n }\n\n /**\n * Determines if an S3 operation should be retried based on the error details.\n */\n private defaultShouldRetry(error: S3ServiceException): boolean {\n const httpStatusCode = error.$metadata?.httpStatusCode;\n const errorCode = error.name;\n\n // Truly transient errors that should always be retried\n const transientErrors = [\n 'NetworkingError',\n 'TimeoutError',\n 'ConnectionError',\n 'RequestTimeout',\n 'ServiceUnavailable',\n 'SlowDown',\n 'ThrottlingException',\n ];\n\n // Server errors are always considered transient\n if (httpStatusCode && httpStatusCode >= 500) {\n return true;\n }\n\n // Specific 4xx errors that are known to be transient\n if (httpStatusCode && httpStatusCode >= 400 && httpStatusCode < 500) {\n const retriable4xxErrors = [\n 'RequestTimeout',\n 'RequestTimeoutException',\n 'PriorRequestNotComplete',\n ];\n return retriable4xxErrors.includes(errorCode);\n }\n\n // Check against known transient errors\n return transientErrors.some(\n retriableError =>\n errorCode === retriableError || error.message.includes(retriableError),\n );\n }\n\n /**\n * Check if the defined bucket exists. Being able to connect means the configuration is good\n * and the storage client will work.\n */\n async getReadiness(): Promise<ReadinessResponse> {\n try {\n await this.storageClient.send(\n new HeadBucketCommand({ Bucket: this.bucketName }),\n );\n\n this.logger.info(\n `Successfully connected to the AWS S3 bucket ${this.bucketName}.`,\n );\n\n return { isAvailable: true };\n } catch (error) {\n this.logger.error(\n `Could not retrieve metadata about the AWS S3 bucket ${this.bucketName}. ` +\n 'Make sure the bucket exists. Also make sure that authentication is setup either by ' +\n 'explicitly defining credentials and region in techdocs.publisher.awsS3 in app config or ' +\n 'by using environment variables. Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage',\n );\n this.logger.error(\n `from AWS client library`,\n error instanceof Error ? error : new Error(String(error)),\n );\n return {\n isAvailable: false,\n };\n }\n }\n /**\n * Upload all the files from the generated `directory` to the S3 bucket.\n * Directory structure used in the bucket is - entityNamespace/entityKind/entityName/index.html\n */\n async publish({\n entity,\n directory,\n }: PublishRequest): Promise<PublishResponse> {\n const objects: string[] = [];\n const useLegacyPathCasing = this.legacyPathCasing;\n const bucketRootPath = this.bucketRootPath;\n const sse = this.sse;\n\n // Track timing for performance monitoring\n const publishStartTime = Date.now();\n\n // First, try to retrieve a list of all individual files currently existing\n let existingFiles: string[] = [];\n try {\n const remoteFolder = getCloudPathForLocalPath(\n entity,\n undefined,\n useLegacyPathCasing,\n bucketRootPath,\n );\n const response = await this.retryOperation(\n async () => {\n const listCommand = new ListObjectsV2Command({\n Bucket: this.bucketName,\n Prefix: remoteFolder,\n });\n return this.storageClient.send(listCommand);\n },\n 'ListObjects',\n this.maxAttempts,\n );\n existingFiles = (response.Contents || [])\n .map(f => f.Key || '')\n .filter(f => !!f);\n } catch (e) {\n assertError(e);\n this.logger.error(\n `Unable to list files for Entity ${entity.metadata.name}: ${e.message}`,\n );\n }\n\n // Then, merge new files into the same folder\n let absoluteFilesToUpload;\n try {\n // Remove the absolute path prefix of the source directory\n // Path of all files to upload, relative to the root of the source directory\n // e.g. ['index.html', 'sub-page/index.html', 'assets/images/favicon.png']\n absoluteFilesToUpload = await getFileTreeRecursively(directory);\n\n await bulkStorageOperation(\n async absoluteFilePath => {\n const relativeFilePath = path.relative(directory, absoluteFilePath);\n const s3Key = getCloudPathForLocalPath(\n entity,\n relativeFilePath,\n useLegacyPathCasing,\n bucketRootPath,\n );\n // Create params without the Body because the body must be the\n // actual file contents (Buffer or Readable), not the path string.\n // For multipart uploads we attach a Readable stream to avoid\n // buffering large files in memory. For simple uploads we attach\n // a Buffer read from disk.\n const params: PutObjectCommandInput = {\n Bucket: this.bucketName,\n Key: s3Key,\n ...(sse && { ServerSideEncryption: sse }),\n };\n\n objects.push(params.Key!);\n // Get file stats before upload\n const stats = await fs.stat(absoluteFilePath);\n const fileSizeInBytes = stats.size;\n\n // Check if this is a large file that requires multipart upload\n if (fileSizeInBytes >= MAX_SINGLE_UPLOAD_BYTES) {\n // Try multipart upload for large files\n try {\n // Create stream and Upload inside retry closure so stream is\n // recreated on each retry attempt (streams are consumable once).\n await this.retryOperation(\n () => {\n // Create a fresh stream on each attempt\n const fileStream = fs.createReadStream(absoluteFilePath);\n const uploadParams = { ...params, Body: fileStream };\n\n const upload = new Upload({\n client: this.storageClient,\n params: uploadParams,\n partSize: MAX_SINGLE_UPLOAD_BYTES,\n queueSize: 3,\n leavePartsOnError: false,\n });\n return upload.done();\n },\n `Upload-${params.Key}`,\n this.maxAttempts,\n );\n return;\n } catch (multipartError) {\n const s3Error = multipartError as any;\n const errorName = s3Error?.name || 'Unknown';\n\n // For specific multipart errors, attempt simple upload fallback\n if (errorName === 'InvalidPart' || errorName === 'NoSuchUpload') {\n this.logger.warn(\n `Multipart upload failed for ${params.Key}, attempting simple upload fallback.`,\n );\n } else {\n // Non-recoverable multipart error, throw it\n this.logger.error(\n `Multipart upload failed for ${params.Key}: ${\n multipartError instanceof Error\n ? multipartError.message\n : String(multipartError)\n }`,\n );\n throw multipartError;\n }\n }\n }\n\n // Use simple upload for small files or as fallback from multipart\n try {\n const fileContent = await fs.readFile(absoluteFilePath);\n const putParams = { ...params, Body: fileContent };\n await this.retryOperation(\n () => this.storageClient.send(new PutObjectCommand(putParams)),\n `Upload-${params.Key}`,\n this.maxAttempts,\n );\n\n if (fileSizeInBytes >= MAX_SINGLE_UPLOAD_BYTES) {\n this.logger.info(\n `Simple upload fallback succeeded for ${params.Key}`,\n );\n }\n } catch (error) {\n this.logger.error(\n `Upload failed for ${params.Key}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n throw error;\n }\n },\n absoluteFilesToUpload,\n { concurrencyLimit: 10 },\n );\n\n this.logger.info(\n `Successfully uploaded all the generated files for Entity ${entity.metadata.name}. Total number of files: ${absoluteFilesToUpload.length}`,\n );\n } catch (e) {\n const errorMessage = `Unable to upload file(s) to AWS S3. ${e}`;\n this.logger.error(errorMessage);\n throw new Error(errorMessage);\n }\n\n // Last, try to remove the files that were *only* present previously\n try {\n const relativeFilesToUpload = absoluteFilesToUpload.map(\n absoluteFilePath =>\n getCloudPathForLocalPath(\n entity,\n path.relative(directory, absoluteFilePath),\n useLegacyPathCasing,\n bucketRootPath,\n ),\n );\n const staleFiles = getStaleFiles(relativeFilesToUpload, existingFiles);\n\n await bulkStorageOperation(\n async relativeFilePath => {\n return this.retryOperation(\n async () => {\n const deleteCommand = new DeleteObjectCommand({\n Bucket: this.bucketName,\n Key: relativeFilePath,\n });\n return this.storageClient.send(deleteCommand);\n },\n 'DeleteObject',\n this.maxAttempts,\n );\n },\n staleFiles,\n { concurrencyLimit: 10 },\n );\n this.logger.info(\n `Successfully deleted stale files for Entity ${entity.metadata.name}. Total number of files: ${staleFiles.length}`,\n );\n } catch (error) {\n const errorMessage = `Unable to delete file(s) from AWS S3. ${error}`;\n this.logger.error(errorMessage);\n }\n const publishEndTime = Date.now();\n const publishDurationMs = publishEndTime - publishStartTime;\n this.logger.info(\n `Successfully published ${objects.length} files for ${\n entity.metadata.name\n } in ${Math.round(publishDurationMs / 1000)}s`,\n );\n return { objects };\n }\n\n async fetchTechDocsMetadata(\n entityName: CompoundEntityRef,\n ): Promise<TechDocsMetadata> {\n try {\n return await new Promise<TechDocsMetadata>(async (resolve, reject) => {\n const entityTriplet = `${entityName.namespace}/${entityName.kind}/${entityName.name}`;\n const entityDir = this.legacyPathCasing\n ? entityTriplet\n : lowerCaseEntityTriplet(entityTriplet);\n\n const entityRootDir = path.posix.join(this.bucketRootPath, entityDir);\n if (!isValidContentPath(this.bucketRootPath, entityRootDir)) {\n this.logger.error(\n `Invalid content path found while fetching TechDocs metadata: ${entityRootDir}`,\n );\n throw new Error(`Metadata Not Found`);\n }\n\n try {\n const resp = await this.retryOperation(\n async () => {\n const getCommand = new GetObjectCommand({\n Bucket: this.bucketName,\n Key: `${entityRootDir}/techdocs_metadata.json`,\n });\n return this.storageClient.send(getCommand);\n },\n 'GetTechDocsMetadata',\n this.maxAttempts,\n );\n\n const techdocsMetadataJson = await streamToBuffer(\n resp.Body as Readable,\n );\n if (!techdocsMetadataJson) {\n throw new Error(\n `Unable to parse the techdocs metadata file ${entityRootDir}/techdocs_metadata.json.`,\n );\n }\n\n const techdocsMetadata = JSON5.parse(\n techdocsMetadataJson.toString('utf-8'),\n );\n\n resolve(techdocsMetadata);\n } catch (err) {\n assertError(err);\n this.logger.error(err.message);\n reject(new Error(err.message));\n }\n });\n } catch (e) {\n throw new ForwardedError('TechDocs metadata fetch failed', e);\n }\n }\n\n /**\n * Express route middleware to serve static files on a route in techdocs-backend.\n */\n docsRouter(): express.Handler {\n return async (req, res) => {\n const decodedUri = decodeURI(req.path.replace(/^\\//, ''));\n\n // filePath example - /default/component/documented-component/index.html\n const filePathNoRoot = this.legacyPathCasing\n ? decodedUri\n : lowerCaseEntityTripletInStoragePath(decodedUri);\n\n // Prepend the root path to the relative file path\n const filePath = path.posix.join(this.bucketRootPath, filePathNoRoot);\n if (!isValidContentPath(this.bucketRootPath, filePath)) {\n this.logger.error(\n `Attempted to fetch TechDocs content for a file outside of the bucket root: ${filePathNoRoot}`,\n );\n res.status(404).send('File Not Found');\n return;\n }\n\n // Files with different extensions (CSS, HTML) need to be served with different headers\n const fileExtension = path.extname(filePath);\n const responseHeaders = getHeadersForFileExtension(fileExtension);\n\n try {\n const resp = await this.storageClient.send(\n new GetObjectCommand({ Bucket: this.bucketName, Key: filePath }),\n );\n\n // Inject response headers\n for (const [headerKey, headerValue] of Object.entries(\n responseHeaders,\n )) {\n res.setHeader(headerKey, headerValue);\n }\n\n (resp.Body as Readable)\n .on('error', err => {\n this.logger.warn(\n `TechDocs S3 router failed to serve static files from bucket ${this.bucketName} at key ${filePath}: ${err.message}`,\n );\n if (!res.headersSent) {\n res.status(404).send('File Not Found');\n } else {\n res.destroy();\n }\n })\n .pipe(res);\n } catch (err) {\n assertError(err);\n this.logger.warn(\n `TechDocs S3 router failed to serve static files from bucket ${this.bucketName} at key ${filePath}: ${err.message}`,\n );\n res.status(404).send('File Not Found');\n }\n };\n }\n\n /**\n * A helper function which checks if index.html of an Entity's docs site is available. This\n * can be used to verify if there are any pre-generated docs available to serve.\n */\n async hasDocsBeenGenerated(entity: Entity): Promise<boolean> {\n try {\n const entityTriplet = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;\n const entityDir = this.legacyPathCasing\n ? entityTriplet\n : lowerCaseEntityTriplet(entityTriplet);\n\n const entityRootDir = path.posix.join(this.bucketRootPath, entityDir);\n if (!isValidContentPath(this.bucketRootPath, entityRootDir)) {\n this.logger.error(\n `Invalid content path found while checking if docs have been generated: ${entityRootDir}`,\n );\n return Promise.resolve(false);\n }\n\n await this.storageClient.send(\n new HeadObjectCommand({\n Bucket: this.bucketName,\n Key: `${entityRootDir}/index.html`,\n }),\n );\n return Promise.resolve(true);\n } catch (e) {\n return Promise.resolve(false);\n }\n }\n\n async migrateDocsCase({\n removeOriginal = false,\n concurrency = 25,\n }): Promise<void> {\n // Iterate through every file in the root of the publisher.\n const allObjects = await this.getAllObjectsFromBucket();\n const limiter = createLimiter(concurrency);\n await Promise.all(\n allObjects.map(f =>\n limiter(async file => {\n let newPath;\n try {\n newPath = lowerCaseEntityTripletInStoragePath(file);\n } catch (e) {\n assertError(e);\n this.logger.warn(e.message);\n return;\n }\n\n // If all parts are already lowercase, ignore.\n if (file === newPath) {\n return;\n }\n\n try {\n this.logger.debug(`Migrating ${file}`);\n await this.storageClient.send(\n new CopyObjectCommand({\n Bucket: this.bucketName,\n CopySource: [this.bucketName, file].join('/'),\n Key: newPath,\n }),\n );\n\n if (removeOriginal) {\n await this.storageClient.send(\n new DeleteObjectCommand({\n Bucket: this.bucketName,\n Key: file,\n }),\n );\n }\n } catch (e) {\n assertError(e);\n this.logger.warn(`Unable to migrate ${file}: ${e.message}`);\n }\n }, f),\n ),\n );\n }\n\n /**\n * Returns a list of all object keys from the configured bucket.\n */\n protected async getAllObjectsFromBucket(\n { prefix } = { prefix: '' },\n ): Promise<string[]> {\n const objects: string[] = [];\n let nextContinuation: string | undefined;\n let allObjects: ListObjectsV2CommandOutput;\n // Iterate through every file in the root of the publisher.\n do {\n const currentToken = nextContinuation;\n allObjects = await this.retryOperation(\n async () => {\n const listCommand = new ListObjectsV2Command({\n Bucket: this.bucketName,\n ContinuationToken: currentToken,\n ...(prefix ? { Prefix: prefix } : {}),\n });\n return this.storageClient.send(listCommand);\n },\n 'GetAllObjects',\n this.maxAttempts,\n );\n objects.push(\n ...(allObjects.Contents || []).map(f => f.Key || '').filter(f => !!f),\n );\n nextContinuation = allObjects.NextContinuationToken;\n } while (nextContinuation);\n\n return objects;\n }\n}\n"],"names":["ForwardedError","normalizeExternalStorageRootPath","DefaultAwsCredentialsManager","S3Client","NodeHttpHandler","HttpsProxyAgent","fromTemporaryCredentials","HeadBucketCommand","getCloudPathForLocalPath","ListObjectsV2Command","assertError","getFileTreeRecursively","bulkStorageOperation","path","fs","Upload","PutObjectCommand","getStaleFiles","DeleteObjectCommand","lowerCaseEntityTriplet","isValidContentPath","GetObjectCommand","JSON5","lowerCaseEntityTripletInStoragePath","getHeadersForFileExtension","HeadObjectCommand","createLimiter","CopyObjectCommand"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"awsS3.cjs.js","sources":["../../../src/stages/publish/awsS3.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Entity, CompoundEntityRef } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { assertError, ForwardedError } from '@backstage/errors';\n\n// Maximum size in bytes for a single upload part (5MB)\nconst MAX_SINGLE_UPLOAD_BYTES = 5 * 1024 * 1024;\n\nimport {\n AwsCredentialsManager,\n DefaultAwsCredentialsManager,\n} from '@backstage/integration-aws-node';\nimport {\n GetObjectCommand,\n CopyObjectCommand,\n DeleteObjectCommand,\n HeadBucketCommand,\n HeadObjectCommand,\n PutObjectCommand,\n PutObjectCommandInput,\n ListObjectsV2CommandOutput,\n ListObjectsV2Command,\n S3Client,\n S3ServiceException,\n} from '@aws-sdk/client-s3';\nimport { fromTemporaryCredentials } from '@aws-sdk/credential-providers';\nimport { NodeHttpHandler } from '@smithy/node-http-handler';\nimport { Upload } from '@aws-sdk/lib-storage';\nimport { AwsCredentialIdentityProvider } from '@aws-sdk/types';\nimport { HttpsProxyAgent } from 'hpagent';\nimport express from 'express';\nimport fs from 'fs-extra';\nimport JSON5 from 'json5';\nimport createLimiter from 'p-limit';\nimport path from 'node:path';\nimport { Readable } from 'node:stream';\nimport {\n bulkStorageOperation,\n getCloudPathForLocalPath,\n getFileTreeRecursively,\n getHeadersForFileExtension,\n getStaleFiles,\n isValidContentPath,\n lowerCaseEntityTriplet,\n lowerCaseEntityTripletInStoragePath,\n normalizeExternalStorageRootPath,\n} from './helpers';\nimport {\n PublisherBase,\n PublishRequest,\n PublishResponse,\n ReadinessResponse,\n TechDocsMetadata,\n} from './types';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { AwsS3Integration, ScmIntegrations } from '@backstage/integration';\n\nconst streamToBuffer = (stream: Readable): Promise<Buffer> => {\n return new Promise((resolve, reject) => {\n try {\n const chunks: any[] = [];\n stream.on('data', chunk => chunks.push(chunk));\n stream.on('error', (e: Error) =>\n reject(new ForwardedError('Unable to read stream', e)),\n );\n stream.on('end', () => resolve(Buffer.concat(chunks)));\n } catch (e) {\n throw new ForwardedError('Unable to parse the response data', e);\n }\n });\n};\n\nexport class AwsS3Publish implements PublisherBase {\n public readonly storageClient: S3Client;\n private readonly bucketName: string;\n private readonly legacyPathCasing: boolean;\n private readonly logger: LoggerService;\n private readonly bucketRootPath: string;\n private readonly sse?: 'aws:kms' | 'AES256';\n private readonly maxAttempts: number;\n\n constructor(options: {\n storageClient: S3Client;\n bucketName: string;\n legacyPathCasing: boolean;\n logger: LoggerService;\n bucketRootPath: string;\n sse?: 'aws:kms' | 'AES256';\n maxAttempts: number;\n }) {\n this.storageClient = options.storageClient;\n this.bucketName = options.bucketName;\n this.legacyPathCasing = options.legacyPathCasing;\n this.logger = options.logger;\n this.bucketRootPath = options.bucketRootPath;\n this.sse = options.sse;\n this.maxAttempts = options.maxAttempts;\n }\n\n static async fromConfig(\n config: Config,\n logger: LoggerService,\n ): Promise<PublisherBase> {\n let bucketName = '';\n try {\n bucketName = config.getString('techdocs.publisher.awsS3.bucketName');\n } catch (error) {\n throw new Error(\n \"Since techdocs.publisher.type is set to 'awsS3' in your app config, \" +\n 'techdocs.publisher.awsS3.bucketName is required.',\n );\n }\n\n const bucketRootPath = normalizeExternalStorageRootPath(\n config.getOptionalString('techdocs.publisher.awsS3.bucketRootPath') || '',\n );\n\n const sse = config.getOptionalString('techdocs.publisher.awsS3.sse') as\n | 'aws:kms'\n | 'AES256'\n | undefined;\n\n // AWS Region is an optional config. If missing, default AWS env variable AWS_REGION\n // or AWS shared credentials file at ~/.aws/credentials will be used.\n const region = config.getOptionalString('techdocs.publisher.awsS3.region');\n\n // Credentials can optionally be configured by specifying the AWS account ID, which will retrieve credentials\n // for the account from the 'aws' section of the app config.\n // Credentials can also optionally be directly configured in the techdocs awsS3 config, but this method is\n // deprecated.\n // If no credentials are configured, the AWS SDK V3's default credential chain will be used.\n const accountId = config.getOptionalString(\n 'techdocs.publisher.awsS3.accountId',\n );\n const credentialsConfig = config.getOptionalConfig(\n 'techdocs.publisher.awsS3.credentials',\n );\n\n const credsManager = DefaultAwsCredentialsManager.fromConfig(config);\n\n const scmIntegrations = ScmIntegrations.fromConfig(config);\n const awsS3Integrations = scmIntegrations.awsS3.list();\n\n const sdkCredentialProvider = await AwsS3Publish.buildCredentials(\n credsManager,\n logger,\n awsS3Integrations,\n accountId,\n credentialsConfig,\n region,\n );\n\n // AWS endpoint is an optional config. If missing, the default endpoint is built from\n // the configured region.\n const endpoint = config.getOptionalString(\n 'techdocs.publisher.awsS3.endpoint',\n );\n\n // AWS HTTPS proxy is an optional config. If missing, no proxy is used\n const httpsProxy = config.getOptionalString(\n 'techdocs.publisher.awsS3.httpsProxy',\n );\n\n // AWS forcePathStyle is an optional config. If missing, it defaults to false. Needs to be enabled for cases\n // where endpoint url points to locally hosted S3 compatible storage like Localstack\n const forcePathStyle = config.getOptionalBoolean(\n 'techdocs.publisher.awsS3.s3ForcePathStyle',\n );\n\n // AWS MAX ATTEMPTS is an optional config. If missing, default value of 5 is used\n const maxAttempts = config.getOptionalNumber(\n 'techdocs.publisher.awsS3.maxAttempts',\n );\n\n const storageClient = new S3Client({\n customUserAgent: 'backstage-aws-techdocs-s3-publisher',\n credentialDefaultProvider: () => sdkCredentialProvider,\n ...(region && { region }),\n ...(endpoint && { endpoint }),\n ...(forcePathStyle && { forcePathStyle }),\n // Enhanced retry configuration for better reliability\n maxAttempts: maxAttempts || 5,\n retryMode: 'adaptive',\n // Enhanced connection settings for large file uploads\n requestHandler: new NodeHttpHandler({\n ...(httpsProxy && {\n httpsAgent: new HttpsProxyAgent({ proxy: httpsProxy }),\n }),\n connectionTimeout: 60000,\n socketTimeout: 120000,\n }),\n });\n\n const legacyPathCasing =\n config.getOptionalBoolean(\n 'techdocs.legacyUseCaseSensitiveTripletPaths',\n ) || false;\n\n return new AwsS3Publish({\n storageClient,\n bucketName,\n bucketRootPath,\n legacyPathCasing,\n logger,\n sse,\n maxAttempts: maxAttempts || 5,\n });\n }\n\n private static buildStaticCredentials(\n accessKeyId: string,\n secretAccessKey: string,\n ): AwsCredentialIdentityProvider {\n return async () => {\n return Promise.resolve({\n accessKeyId,\n secretAccessKey,\n });\n };\n }\n\n private static async buildCredentials(\n credsManager: AwsCredentialsManager,\n logger: LoggerService,\n awsS3Integrations: AwsS3Integration[],\n accountId?: string,\n credentialsConfig?: Config,\n region?: string,\n ): Promise<AwsCredentialIdentityProvider> {\n // Pull credentials for the specified account ID from the 'aws' config section\n if (accountId) {\n return (await credsManager.getCredentialProvider({ accountId }))\n .sdkCredentialProvider;\n }\n\n const explicitCredentials = await AwsS3Publish.getExplicitCredentials({\n credsManager,\n credentialsConfig,\n awsS3Integrations,\n logger,\n });\n\n const roleArn = credentialsConfig?.getOptionalString('roleArn');\n if (roleArn) {\n return fromTemporaryCredentials({\n masterCredentials: explicitCredentials,\n params: {\n RoleSessionName: 'backstage-aws-techdocs-s3-publisher',\n RoleArn: roleArn,\n },\n clientConfig: { region },\n });\n }\n\n return explicitCredentials;\n }\n /**\n * Custom retry wrapper for S3 operations with detailed error handling.\n */\n public async retryOperation<TOutput>(\n operation: () => Promise<TOutput>,\n operationName: string,\n maxAttempts: number = 3,\n shouldRetry: (\n error: S3ServiceException,\n ) => boolean = this.defaultShouldRetry.bind(this),\n ): Promise<TOutput> {\n for (let attempt = 1; attempt < maxAttempts; attempt++) {\n try {\n return await operation();\n } catch (error) {\n const e = error as S3ServiceException;\n if (!shouldRetry(e)) {\n this.logger.error(`${operationName} failed: ${e.message}`);\n throw e;\n }\n\n this.logger.warn(`${operationName} failed, retrying...`, {\n attempt,\n maxAttempts,\n error: e.message,\n errorCode: e.name,\n httpStatusCode: e.$metadata?.httpStatusCode,\n });\n\n // Enhanced exponential backoff with jitter\n const baseDelay = operationName.startsWith('Upload-') ? 2000 : 1000;\n const backoffDelay = Math.min(\n baseDelay * Math.pow(2, attempt - 1),\n 30000,\n );\n const jitter = Math.random() * 1000;\n await new Promise(resolve =>\n setTimeout(resolve, backoffDelay + jitter),\n );\n }\n }\n return await operation();\n }\n\n /**\n * Determines if an S3 operation should be retried based on the error details.\n */\n private defaultShouldRetry(error: S3ServiceException): boolean {\n const httpStatusCode = error.$metadata?.httpStatusCode;\n const errorCode = error.name;\n\n // Truly transient errors that should always be retried\n const transientErrors = [\n 'NetworkingError',\n 'TimeoutError',\n 'ConnectionError',\n 'RequestTimeout',\n 'ServiceUnavailable',\n 'SlowDown',\n 'ThrottlingException',\n ];\n\n // Server errors are always considered transient\n if (httpStatusCode && httpStatusCode >= 500) {\n return true;\n }\n\n // Specific 4xx errors that are known to be transient\n if (httpStatusCode && httpStatusCode >= 400 && httpStatusCode < 500) {\n const retriable4xxErrors = [\n 'RequestTimeout',\n 'RequestTimeoutException',\n 'PriorRequestNotComplete',\n ];\n return retriable4xxErrors.includes(errorCode);\n }\n\n // Check against known transient errors\n return transientErrors.some(\n retriableError =>\n errorCode === retriableError || error.message.includes(retriableError),\n );\n }\n\n private static async getExplicitCredentials({\n credentialsConfig,\n awsS3Integrations,\n credsManager,\n logger,\n }: {\n credentialsConfig?: Config;\n awsS3Integrations: AwsS3Integration[];\n credsManager: AwsCredentialsManager;\n logger: LoggerService;\n }): Promise<AwsCredentialIdentityProvider> {\n const accessKeyId = credentialsConfig?.getOptionalString('accessKeyId');\n const secretAccessKey =\n credentialsConfig?.getOptionalString('secretAccessKey');\n\n if (accessKeyId && secretAccessKey) {\n return AwsS3Publish.buildStaticCredentials(accessKeyId, secretAccessKey);\n }\n\n if (awsS3Integrations.length > 0) {\n if (awsS3Integrations.length === 1) {\n const singleAwsS3IntegrationConfig = awsS3Integrations[0].config;\n\n const singleAwsS3IntegrationAccessKeyId =\n singleAwsS3IntegrationConfig.accessKeyId;\n\n const singleAwsS3IntegrationSecretAccessKey =\n singleAwsS3IntegrationConfig.secretAccessKey;\n\n if (\n singleAwsS3IntegrationAccessKeyId &&\n singleAwsS3IntegrationSecretAccessKey\n ) {\n return AwsS3Publish.buildStaticCredentials(\n singleAwsS3IntegrationAccessKeyId,\n singleAwsS3IntegrationSecretAccessKey,\n );\n }\n } else {\n if (accessKeyId) {\n const targetAwsS3IntegrationConfig = awsS3Integrations.find(\n c => c.config.accessKeyId === accessKeyId,\n );\n\n if (!targetAwsS3IntegrationConfig) {\n logger.warn(\n `No AWS S3 integration config under integrations.awsS3 found for access key id ${accessKeyId}.`,\n );\n }\n const targetAwsS3IntegrationAccessKeyId =\n targetAwsS3IntegrationConfig?.config.accessKeyId;\n const targetAwsS3IntegrationSecretAccessKey =\n targetAwsS3IntegrationConfig?.config.secretAccessKey;\n if (\n targetAwsS3IntegrationAccessKeyId &&\n targetAwsS3IntegrationSecretAccessKey\n ) {\n return AwsS3Publish.buildStaticCredentials(\n targetAwsS3IntegrationAccessKeyId,\n targetAwsS3IntegrationSecretAccessKey,\n );\n }\n }\n }\n }\n return (await credsManager.getCredentialProvider()).sdkCredentialProvider;\n }\n\n /**\n * Check if the defined bucket exists. Being able to connect means the configuration is good\n * and the storage client will work.\n */\n async getReadiness(): Promise<ReadinessResponse> {\n try {\n await this.storageClient.send(\n new HeadBucketCommand({ Bucket: this.bucketName }),\n );\n\n this.logger.info(\n `Successfully connected to the AWS S3 bucket ${this.bucketName}.`,\n );\n\n return { isAvailable: true };\n } catch (error) {\n this.logger.error(\n `Could not retrieve metadata about the AWS S3 bucket ${this.bucketName}. ` +\n 'Make sure the bucket exists. Also make sure that authentication is setup either by ' +\n 'explicitly defining credentials and region in techdocs.publisher.awsS3 in app config or ' +\n 'by using environment variables. Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage',\n );\n this.logger.error(\n `from AWS client library`,\n error instanceof Error ? error : new Error(String(error)),\n );\n return {\n isAvailable: false,\n };\n }\n }\n /**\n * Upload all the files from the generated `directory` to the S3 bucket.\n * Directory structure used in the bucket is - entityNamespace/entityKind/entityName/index.html\n */\n async publish({\n entity,\n directory,\n }: PublishRequest): Promise<PublishResponse> {\n const objects: string[] = [];\n const useLegacyPathCasing = this.legacyPathCasing;\n const bucketRootPath = this.bucketRootPath;\n const sse = this.sse;\n\n // Track timing for performance monitoring\n const publishStartTime = Date.now();\n\n // First, try to retrieve a list of all individual files currently existing\n let existingFiles: string[] = [];\n try {\n const remoteFolder = getCloudPathForLocalPath(\n entity,\n undefined,\n useLegacyPathCasing,\n bucketRootPath,\n );\n const response = await this.retryOperation(\n async () => {\n const listCommand = new ListObjectsV2Command({\n Bucket: this.bucketName,\n Prefix: remoteFolder,\n });\n return this.storageClient.send(listCommand);\n },\n 'ListObjects',\n this.maxAttempts,\n );\n existingFiles = (response.Contents || [])\n .map(f => f.Key || '')\n .filter(f => !!f);\n } catch (e) {\n assertError(e);\n this.logger.error(\n `Unable to list files for Entity ${entity.metadata.name}: ${e.message}`,\n );\n }\n\n // Then, merge new files into the same folder\n let absoluteFilesToUpload;\n try {\n // Remove the absolute path prefix of the source directory\n // Path of all files to upload, relative to the root of the source directory\n // e.g. ['index.html', 'sub-page/index.html', 'assets/images/favicon.png']\n absoluteFilesToUpload = await getFileTreeRecursively(directory);\n\n await bulkStorageOperation(\n async absoluteFilePath => {\n const relativeFilePath = path.relative(directory, absoluteFilePath);\n const s3Key = getCloudPathForLocalPath(\n entity,\n relativeFilePath,\n useLegacyPathCasing,\n bucketRootPath,\n );\n // Create params without the Body because the body must be the\n // actual file contents (Buffer or Readable), not the path string.\n // For multipart uploads we attach a Readable stream to avoid\n // buffering large files in memory. For simple uploads we attach\n // a Buffer read from disk.\n const params: PutObjectCommandInput = {\n Bucket: this.bucketName,\n Key: s3Key,\n ...(sse && { ServerSideEncryption: sse }),\n };\n\n objects.push(params.Key!);\n // Get file stats before upload\n const stats = await fs.stat(absoluteFilePath);\n const fileSizeInBytes = stats.size;\n\n // Check if this is a large file that requires multipart upload\n if (fileSizeInBytes >= MAX_SINGLE_UPLOAD_BYTES) {\n // Try multipart upload for large files\n try {\n // Create stream and Upload inside retry closure so stream is\n // recreated on each retry attempt (streams are consumable once).\n await this.retryOperation(\n () => {\n // Create a fresh stream on each attempt\n const fileStream = fs.createReadStream(absoluteFilePath);\n const uploadParams = { ...params, Body: fileStream };\n\n const upload = new Upload({\n client: this.storageClient,\n params: uploadParams,\n partSize: MAX_SINGLE_UPLOAD_BYTES,\n queueSize: 3,\n leavePartsOnError: false,\n });\n return upload.done();\n },\n `Upload-${params.Key}`,\n this.maxAttempts,\n );\n return;\n } catch (multipartError) {\n const s3Error = multipartError as any;\n const errorName = s3Error?.name || 'Unknown';\n\n // For specific multipart errors, attempt simple upload fallback\n if (errorName === 'InvalidPart' || errorName === 'NoSuchUpload') {\n this.logger.warn(\n `Multipart upload failed for ${params.Key}, attempting simple upload fallback.`,\n );\n } else {\n // Non-recoverable multipart error, throw it\n this.logger.error(\n `Multipart upload failed for ${params.Key}: ${\n multipartError instanceof Error\n ? multipartError.message\n : String(multipartError)\n }`,\n );\n throw multipartError;\n }\n }\n }\n\n // Use simple upload for small files or as fallback from multipart\n try {\n const fileContent = await fs.readFile(absoluteFilePath);\n const putParams = { ...params, Body: fileContent };\n await this.retryOperation(\n () => this.storageClient.send(new PutObjectCommand(putParams)),\n `Upload-${params.Key}`,\n this.maxAttempts,\n );\n\n if (fileSizeInBytes >= MAX_SINGLE_UPLOAD_BYTES) {\n this.logger.info(\n `Simple upload fallback succeeded for ${params.Key}`,\n );\n }\n } catch (error) {\n this.logger.error(\n `Upload failed for ${params.Key}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n throw error;\n }\n },\n absoluteFilesToUpload,\n { concurrencyLimit: 10 },\n );\n\n this.logger.info(\n `Successfully uploaded all the generated files for Entity ${entity.metadata.name}. Total number of files: ${absoluteFilesToUpload.length}`,\n );\n } catch (e) {\n const errorMessage = `Unable to upload file(s) to AWS S3. ${e}`;\n this.logger.error(errorMessage);\n throw new Error(errorMessage);\n }\n\n // Last, try to remove the files that were *only* present previously\n try {\n const relativeFilesToUpload = absoluteFilesToUpload.map(\n absoluteFilePath =>\n getCloudPathForLocalPath(\n entity,\n path.relative(directory, absoluteFilePath),\n useLegacyPathCasing,\n bucketRootPath,\n ),\n );\n const staleFiles = getStaleFiles(relativeFilesToUpload, existingFiles);\n\n await bulkStorageOperation(\n async relativeFilePath => {\n return this.retryOperation(\n async () => {\n const deleteCommand = new DeleteObjectCommand({\n Bucket: this.bucketName,\n Key: relativeFilePath,\n });\n return this.storageClient.send(deleteCommand);\n },\n 'DeleteObject',\n this.maxAttempts,\n );\n },\n staleFiles,\n { concurrencyLimit: 10 },\n );\n this.logger.info(\n `Successfully deleted stale files for Entity ${entity.metadata.name}. Total number of files: ${staleFiles.length}`,\n );\n } catch (error) {\n const errorMessage = `Unable to delete file(s) from AWS S3. ${error}`;\n this.logger.error(errorMessage);\n }\n const publishEndTime = Date.now();\n const publishDurationMs = publishEndTime - publishStartTime;\n this.logger.info(\n `Successfully published ${objects.length} files for ${\n entity.metadata.name\n } in ${Math.round(publishDurationMs / 1000)}s`,\n );\n return { objects };\n }\n\n async fetchTechDocsMetadata(\n entityName: CompoundEntityRef,\n ): Promise<TechDocsMetadata> {\n try {\n return await new Promise<TechDocsMetadata>(async (resolve, reject) => {\n const entityTriplet = `${entityName.namespace}/${entityName.kind}/${entityName.name}`;\n const entityDir = this.legacyPathCasing\n ? entityTriplet\n : lowerCaseEntityTriplet(entityTriplet);\n\n const entityRootDir = path.posix.join(this.bucketRootPath, entityDir);\n if (!isValidContentPath(this.bucketRootPath, entityRootDir)) {\n this.logger.error(\n `Invalid content path found while fetching TechDocs metadata: ${entityRootDir}`,\n );\n throw new Error(`Metadata Not Found`);\n }\n\n try {\n const resp = await this.retryOperation(\n async () => {\n const getCommand = new GetObjectCommand({\n Bucket: this.bucketName,\n Key: `${entityRootDir}/techdocs_metadata.json`,\n });\n return this.storageClient.send(getCommand);\n },\n 'GetTechDocsMetadata',\n this.maxAttempts,\n );\n\n const techdocsMetadataJson = await streamToBuffer(\n resp.Body as Readable,\n );\n if (!techdocsMetadataJson) {\n throw new Error(\n `Unable to parse the techdocs metadata file ${entityRootDir}/techdocs_metadata.json.`,\n );\n }\n\n const techdocsMetadata = JSON5.parse(\n techdocsMetadataJson.toString('utf-8'),\n );\n\n resolve(techdocsMetadata);\n } catch (err) {\n assertError(err);\n this.logger.error(err.message);\n reject(new Error(err.message));\n }\n });\n } catch (e) {\n throw new ForwardedError('TechDocs metadata fetch failed', e);\n }\n }\n\n /**\n * Express route middleware to serve static files on a route in techdocs-backend.\n */\n docsRouter(): express.Handler {\n return async (req, res) => {\n const decodedUri = decodeURI(req.path.replace(/^\\//, ''));\n\n // filePath example - /default/component/documented-component/index.html\n const filePathNoRoot = this.legacyPathCasing\n ? decodedUri\n : lowerCaseEntityTripletInStoragePath(decodedUri);\n\n // Prepend the root path to the relative file path\n const filePath = path.posix.join(this.bucketRootPath, filePathNoRoot);\n if (!isValidContentPath(this.bucketRootPath, filePath)) {\n this.logger.error(\n `Attempted to fetch TechDocs content for a file outside of the bucket root: ${filePathNoRoot}`,\n );\n res.status(404).send('File Not Found');\n return;\n }\n\n // Files with different extensions (CSS, HTML) need to be served with different headers\n const fileExtension = path.extname(filePath);\n const responseHeaders = getHeadersForFileExtension(fileExtension);\n\n try {\n const resp = await this.storageClient.send(\n new GetObjectCommand({ Bucket: this.bucketName, Key: filePath }),\n );\n\n // Inject response headers\n for (const [headerKey, headerValue] of Object.entries(\n responseHeaders,\n )) {\n res.setHeader(headerKey, headerValue);\n }\n\n (resp.Body as Readable)\n .on('error', err => {\n this.logger.warn(\n `TechDocs S3 router failed to serve static files from bucket ${this.bucketName} at key ${filePath}: ${err.message}`,\n );\n if (!res.headersSent) {\n res.status(404).send('File Not Found');\n } else {\n res.destroy();\n }\n })\n .pipe(res);\n } catch (err) {\n assertError(err);\n this.logger.warn(\n `TechDocs S3 router failed to serve static files from bucket ${this.bucketName} at key ${filePath}: ${err.message}`,\n );\n res.status(404).send('File Not Found');\n }\n };\n }\n\n /**\n * A helper function which checks if index.html of an Entity's docs site is available. This\n * can be used to verify if there are any pre-generated docs available to serve.\n */\n async hasDocsBeenGenerated(entity: Entity): Promise<boolean> {\n try {\n const entityTriplet = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;\n const entityDir = this.legacyPathCasing\n ? entityTriplet\n : lowerCaseEntityTriplet(entityTriplet);\n\n const entityRootDir = path.posix.join(this.bucketRootPath, entityDir);\n if (!isValidContentPath(this.bucketRootPath, entityRootDir)) {\n this.logger.error(\n `Invalid content path found while checking if docs have been generated: ${entityRootDir}`,\n );\n return Promise.resolve(false);\n }\n\n await this.storageClient.send(\n new HeadObjectCommand({\n Bucket: this.bucketName,\n Key: `${entityRootDir}/index.html`,\n }),\n );\n return Promise.resolve(true);\n } catch (e) {\n return Promise.resolve(false);\n }\n }\n\n async migrateDocsCase({\n removeOriginal = false,\n concurrency = 25,\n }): Promise<void> {\n // Iterate through every file in the root of the publisher.\n const allObjects = await this.getAllObjectsFromBucket();\n const limiter = createLimiter(concurrency);\n await Promise.all(\n allObjects.map(f =>\n limiter(async file => {\n let newPath;\n try {\n newPath = lowerCaseEntityTripletInStoragePath(file);\n } catch (e) {\n assertError(e);\n this.logger.warn(e.message);\n return;\n }\n\n // If all parts are already lowercase, ignore.\n if (file === newPath) {\n return;\n }\n\n try {\n this.logger.debug(`Migrating ${file}`);\n await this.storageClient.send(\n new CopyObjectCommand({\n Bucket: this.bucketName,\n CopySource: [this.bucketName, file].join('/'),\n Key: newPath,\n }),\n );\n\n if (removeOriginal) {\n await this.storageClient.send(\n new DeleteObjectCommand({\n Bucket: this.bucketName,\n Key: file,\n }),\n );\n }\n } catch (e) {\n assertError(e);\n this.logger.warn(`Unable to migrate ${file}: ${e.message}`);\n }\n }, f),\n ),\n );\n }\n\n /**\n * Returns a list of all object keys from the configured bucket.\n */\n protected async getAllObjectsFromBucket(\n { prefix } = { prefix: '' },\n ): Promise<string[]> {\n const objects: string[] = [];\n let nextContinuation: string | undefined;\n let allObjects: ListObjectsV2CommandOutput;\n // Iterate through every file in the root of the publisher.\n do {\n const currentToken = nextContinuation;\n allObjects = await this.retryOperation(\n async () => {\n const listCommand = new ListObjectsV2Command({\n Bucket: this.bucketName,\n ContinuationToken: currentToken,\n ...(prefix ? { Prefix: prefix } : {}),\n });\n return this.storageClient.send(listCommand);\n },\n 'GetAllObjects',\n this.maxAttempts,\n );\n objects.push(\n ...(allObjects.Contents || []).map(f => f.Key || '').filter(f => !!f),\n );\n nextContinuation = allObjects.NextContinuationToken;\n } while (nextContinuation);\n\n return objects;\n }\n}\n"],"names":["ForwardedError","normalizeExternalStorageRootPath","DefaultAwsCredentialsManager","ScmIntegrations","S3Client","NodeHttpHandler","HttpsProxyAgent","fromTemporaryCredentials","HeadBucketCommand","getCloudPathForLocalPath","ListObjectsV2Command","assertError","getFileTreeRecursively","bulkStorageOperation","path","fs","Upload","PutObjectCommand","getStaleFiles","DeleteObjectCommand","lowerCaseEntityTriplet","isValidContentPath","GetObjectCommand","JSON5","lowerCaseEntityTripletInStoragePath","getHeadersForFileExtension","HeadObjectCommand","createLimiter","CopyObjectCommand"],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@ var storageBlob = require('@azure/storage-blob');
|
|
|
5
5
|
var errors = require('@backstage/errors');
|
|
6
6
|
var JSON5 = require('json5');
|
|
7
7
|
var createLimiter = require('p-limit');
|
|
8
|
-
var path = require('path');
|
|
8
|
+
var path = require('node:path');
|
|
9
9
|
var helpers = require('./helpers.cjs.js');
|
|
10
10
|
|
|
11
11
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"azureBlobStorage.cjs.js","sources":["../../../src/stages/publish/azureBlobStorage.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { DefaultAzureCredential } from '@azure/identity';\nimport {\n BlobServiceClient,\n ContainerClient,\n StorageSharedKeyCredential,\n} from '@azure/storage-blob';\nimport { Entity, CompoundEntityRef } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { assertError, ForwardedError } from '@backstage/errors';\nimport express from 'express';\nimport JSON5 from 'json5';\nimport limiterFactory from 'p-limit';\nimport { default as path, default as platformPath } from 'path';\nimport {\n bulkStorageOperation,\n getCloudPathForLocalPath,\n getFileTreeRecursively,\n getHeadersForFileExtension,\n lowerCaseEntityTriplet,\n getStaleFiles,\n lowerCaseEntityTripletInStoragePath,\n} from './helpers';\nimport {\n PublisherBase,\n PublishRequest,\n PublishResponse,\n ReadinessResponse,\n TechDocsMetadata,\n} from './types';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\n// The number of batches that may be ongoing at the same time.\nconst BATCH_CONCURRENCY = 3;\n\nexport class AzureBlobStoragePublish implements PublisherBase {\n private readonly storageClient: BlobServiceClient;\n private readonly containerName: string;\n private readonly legacyPathCasing: boolean;\n private readonly logger: LoggerService;\n\n constructor(options: {\n storageClient: BlobServiceClient;\n containerName: string;\n legacyPathCasing: boolean;\n logger: LoggerService;\n }) {\n this.storageClient = options.storageClient;\n this.containerName = options.containerName;\n this.legacyPathCasing = options.legacyPathCasing;\n this.logger = options.logger;\n }\n\n static fromConfig(config: Config, logger: LoggerService): PublisherBase {\n let storageClient: BlobServiceClient;\n let containerName = '';\n try {\n containerName = config.getString(\n 'techdocs.publisher.azureBlobStorage.containerName',\n );\n } catch (error) {\n throw new Error(\n \"Since techdocs.publisher.type is set to 'azureBlobStorage' in your app config, \" +\n 'techdocs.publisher.azureBlobStorage.containerName is required.',\n );\n }\n\n const legacyPathCasing =\n config.getOptionalBoolean(\n 'techdocs.legacyUseCaseSensitiveTripletPaths',\n ) || false;\n\n // Give more priority for connectionString, if configured, return the AzureBlobStoragePublish object here itself\n const connectionStringKey =\n 'techdocs.publisher.azureBlobStorage.connectionString';\n const connectionString = config.getOptionalString(connectionStringKey);\n\n if (connectionString) {\n logger.info(\n `Using '${connectionStringKey}' configuration to create storage client`,\n );\n storageClient = BlobServiceClient.fromConnectionString(connectionString);\n } else {\n let accountName = '';\n try {\n accountName = config.getString(\n 'techdocs.publisher.azureBlobStorage.credentials.accountName',\n );\n } catch (error) {\n throw new Error(\n \"Since techdocs.publisher.type is set to 'azureBlobStorage' in your app config, \" +\n 'techdocs.publisher.azureBlobStorage.credentials.accountName is required.',\n );\n }\n\n // Credentials is an optional config. If missing, default Azure Blob Storage environment variables will be used.\n // https://docs.microsoft.com/en-us/azure/storage/common/storage-auth-aad-app\n const accountKey = config.getOptionalString(\n 'techdocs.publisher.azureBlobStorage.credentials.accountKey',\n );\n\n let credential;\n if (accountKey) {\n credential = new StorageSharedKeyCredential(accountName, accountKey);\n } else {\n credential = new DefaultAzureCredential();\n }\n\n storageClient = new BlobServiceClient(\n `https://${accountName}.blob.core.windows.net`,\n credential,\n );\n }\n\n return new AzureBlobStoragePublish({\n storageClient: storageClient,\n containerName: containerName,\n legacyPathCasing: legacyPathCasing,\n logger: logger,\n });\n }\n\n async getReadiness(): Promise<ReadinessResponse> {\n try {\n const response = await this.storageClient\n .getContainerClient(this.containerName)\n .getProperties();\n\n if (response._response.status === 200) {\n return {\n isAvailable: true,\n };\n }\n\n if (response._response.status >= 400) {\n this.logger.error(\n `Failed to retrieve metadata from ${response._response.request.url} with status code ${response._response.status}.`,\n );\n }\n } catch (e) {\n assertError(e);\n this.logger.error(`from Azure Blob Storage client library: ${e.message}`);\n }\n\n this.logger.error(\n `Could not retrieve metadata about the Azure Blob Storage container ${this.containerName}. ` +\n 'Make sure that the Azure project and container exist and the access key is setup correctly ' +\n 'techdocs.publisher.azureBlobStorage.credentials defined in app config has correct permissions. ' +\n 'Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage',\n );\n\n return { isAvailable: false };\n }\n\n /**\n * Upload all the files from the generated `directory` to the Azure Blob Storage container.\n * Directory structure used in the container is - entityNamespace/entityKind/entityName/index.html\n */\n async publish({\n entity,\n directory,\n }: PublishRequest): Promise<PublishResponse> {\n const objects: string[] = [];\n const useLegacyPathCasing = this.legacyPathCasing;\n\n // First, try to retrieve a list of all individual files currently existing\n const remoteFolder = getCloudPathForLocalPath(\n entity,\n undefined,\n useLegacyPathCasing,\n );\n let existingFiles: string[] = [];\n try {\n existingFiles = await this.getAllBlobsFromContainer({\n prefix: remoteFolder,\n maxPageSize: BATCH_CONCURRENCY,\n });\n } catch (e) {\n assertError(e);\n this.logger.error(\n `Unable to list files for Entity ${entity.metadata.name}: ${e.message}`,\n );\n }\n\n // Then, merge new files into the same folder\n let absoluteFilesToUpload;\n let container: ContainerClient;\n try {\n // Remove the absolute path prefix of the source directory\n // Path of all files to upload, relative to the root of the source directory\n // e.g. ['index.html', 'sub-page/index.html', 'assets/images/favicon.png']\n absoluteFilesToUpload = await getFileTreeRecursively(directory);\n\n container = this.storageClient.getContainerClient(this.containerName);\n const failedOperations: Error[] = [];\n await bulkStorageOperation(\n async absoluteFilePath => {\n const relativeFilePath = path.normalize(\n path.relative(directory, absoluteFilePath),\n );\n const remotePath = getCloudPathForLocalPath(\n entity,\n relativeFilePath,\n useLegacyPathCasing,\n );\n objects.push(remotePath);\n const response = await container\n .getBlockBlobClient(remotePath)\n .uploadFile(absoluteFilePath);\n\n if (response._response.status >= 400) {\n failedOperations.push(\n new Error(\n `Upload failed for ${absoluteFilePath} with status code ${response._response.status}`,\n ),\n );\n }\n\n return response;\n },\n absoluteFilesToUpload,\n { concurrencyLimit: BATCH_CONCURRENCY },\n );\n\n if (failedOperations.length > 0) {\n throw new Error(\n failedOperations\n .map(r => r.message)\n .filter(Boolean)\n .join(' '),\n );\n }\n\n this.logger.info(\n `Successfully uploaded all the generated files for Entity ${entity.metadata.name}. Total number of files: ${absoluteFilesToUpload.length}`,\n );\n } catch (e) {\n const errorMessage = `Unable to upload file(s) to Azure. ${e}`;\n this.logger.error(errorMessage);\n throw new Error(errorMessage);\n }\n\n // Last, try to remove the files that were *only* present previously\n try {\n const relativeFilesToUpload = absoluteFilesToUpload.map(\n absoluteFilePath =>\n getCloudPathForLocalPath(\n entity,\n path.relative(directory, absoluteFilePath),\n useLegacyPathCasing,\n ),\n );\n\n const staleFiles = getStaleFiles(relativeFilesToUpload, existingFiles);\n\n await bulkStorageOperation(\n async relativeFilePath => {\n return await container.deleteBlob(relativeFilePath);\n },\n staleFiles,\n { concurrencyLimit: BATCH_CONCURRENCY },\n );\n\n this.logger.info(\n `Successfully deleted stale files for Entity ${entity.metadata.name}. Total number of files: ${staleFiles.length}`,\n );\n } catch (error) {\n const errorMessage = `Unable to delete file(s) from Azure. ${error}`;\n this.logger.error(errorMessage);\n }\n\n return { objects };\n }\n\n async fetchTechDocsMetadata(\n entityName: CompoundEntityRef,\n ): Promise<TechDocsMetadata> {\n const entityTriplet = `${entityName.namespace}/${entityName.kind}/${entityName.name}`;\n const entityRootDir = this.legacyPathCasing\n ? entityTriplet\n : lowerCaseEntityTriplet(entityTriplet);\n\n try {\n const techdocsMetadataJson = await new Promise<Buffer>(\n (resolve, reject) => {\n const fileStreamChunks: Array<any> = [];\n this.storageClient\n .getContainerClient(this.containerName)\n .getBlockBlobClient(`${entityRootDir}/techdocs_metadata.json`)\n .download()\n .then(res => {\n const body = res.readableStreamBody;\n if (!body) {\n reject(new Error(`Unable to parse the response data`));\n return;\n }\n body\n .on('error', reject)\n .on('data', chunk => {\n fileStreamChunks.push(chunk);\n })\n .on('end', () => {\n resolve(Buffer.concat(fileStreamChunks));\n });\n })\n .catch(reject);\n },\n );\n\n if (!techdocsMetadataJson) {\n throw new Error(\n `Unable to parse the techdocs metadata file ${entityRootDir}/techdocs_metadata.json.`,\n );\n }\n const techdocsMetadata = JSON5.parse(\n techdocsMetadataJson.toString('utf-8'),\n );\n return techdocsMetadata;\n } catch (e) {\n throw new ForwardedError('TechDocs metadata fetch failed', e);\n }\n }\n\n /**\n * Express route middleware to serve static files on a route in techdocs-backend.\n */\n docsRouter(): express.Handler {\n return (req, res) => {\n // Decode and trim the leading forward slash\n const decodedUri = decodeURI(req.path.replace(/^\\//, ''));\n\n // filePath example - /default/Component/documented-component/index.html\n const filePath = this.legacyPathCasing\n ? decodedUri\n : lowerCaseEntityTripletInStoragePath(decodedUri);\n\n // Files with different extensions (CSS, HTML) need to be served with different headers\n const fileExtension = platformPath.extname(filePath);\n const responseHeaders = getHeadersForFileExtension(fileExtension);\n\n const blobClient = this.storageClient\n .getContainerClient(this.containerName)\n .getBlockBlobClient(filePath);\n\n blobClient\n .download()\n .then(downloadRes => {\n if (!downloadRes.readableStreamBody) {\n throw new Error('Unable to parse the response data');\n }\n for (const [headerKey, headerValue] of Object.entries(\n responseHeaders,\n )) {\n res.setHeader(headerKey, headerValue);\n }\n downloadRes.readableStreamBody.pipe(res);\n })\n .catch(e => {\n this.logger.warn(\n `TechDocs Azure router failed to serve content from container ${this.containerName} at path ${filePath}: ${e.message}`,\n );\n if (!res.headersSent) {\n res.status(404).send('File Not Found');\n } else {\n res.destroy();\n }\n });\n };\n }\n\n /**\n * A helper function which checks if index.html of an Entity's docs site is available. This\n * can be used to verify if there are any pre-generated docs available to serve.\n */\n hasDocsBeenGenerated(entity: Entity): Promise<boolean> {\n const entityTriplet = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;\n const entityRootDir = this.legacyPathCasing\n ? entityTriplet\n : lowerCaseEntityTriplet(entityTriplet);\n\n return this.storageClient\n .getContainerClient(this.containerName)\n .getBlockBlobClient(`${entityRootDir}/index.html`)\n .exists();\n }\n\n protected async renameBlob(\n originalName: string,\n newName: string,\n removeOriginal = false,\n ): Promise<void> {\n const container = this.storageClient.getContainerClient(this.containerName);\n const blob = container.getBlobClient(newName);\n const { url } = container.getBlobClient(originalName);\n const response = await blob.beginCopyFromURL(url);\n await response.pollUntilDone();\n if (removeOriginal) {\n await container.deleteBlob(originalName);\n }\n }\n\n protected async renameBlobToLowerCase(\n originalPath: string,\n removeOriginal: boolean,\n ) {\n let newPath;\n try {\n newPath = lowerCaseEntityTripletInStoragePath(originalPath);\n } catch (e) {\n assertError(e);\n this.logger.warn(e.message);\n return;\n }\n\n if (originalPath === newPath) return;\n try {\n this.logger.debug(`Migrating ${originalPath}`);\n await this.renameBlob(originalPath, newPath, removeOriginal);\n } catch (e) {\n assertError(e);\n this.logger.warn(`Unable to migrate ${originalPath}: ${e.message}`);\n }\n }\n\n async migrateDocsCase({\n removeOriginal = false,\n concurrency = 25,\n }): Promise<void> {\n const promises = [];\n const limiter = limiterFactory(concurrency);\n const container = this.storageClient.getContainerClient(this.containerName);\n\n for await (const blob of container.listBlobsFlat()) {\n promises.push(\n limiter(\n this.renameBlobToLowerCase.bind(this),\n blob.name,\n removeOriginal,\n ),\n );\n }\n\n await Promise.all(promises);\n }\n\n protected async getAllBlobsFromContainer({\n prefix,\n maxPageSize,\n }: {\n prefix: string;\n maxPageSize: number;\n }): Promise<string[]> {\n const blobs: string[] = [];\n const container = this.storageClient.getContainerClient(this.containerName);\n\n let iterator = container.listBlobsFlat({ prefix }).byPage({ maxPageSize });\n let response = (await iterator.next()).value;\n\n do {\n for (const blob of response?.segment?.blobItems ?? []) {\n blobs.push(blob.name);\n }\n iterator = container\n .listBlobsFlat({ prefix })\n .byPage({ continuationToken: response.continuationToken, maxPageSize });\n response = (await iterator.next()).value;\n } while (response && response.continuationToken);\n\n return blobs;\n }\n}\n"],"names":["BlobServiceClient","StorageSharedKeyCredential","DefaultAzureCredential","assertError","getCloudPathForLocalPath","getFileTreeRecursively","bulkStorageOperation","path","getStaleFiles","lowerCaseEntityTriplet","JSON5","ForwardedError","lowerCaseEntityTripletInStoragePath","platformPath","getHeadersForFileExtension","limiterFactory"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"azureBlobStorage.cjs.js","sources":["../../../src/stages/publish/azureBlobStorage.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { DefaultAzureCredential } from '@azure/identity';\nimport {\n BlobServiceClient,\n ContainerClient,\n StorageSharedKeyCredential,\n} from '@azure/storage-blob';\nimport { Entity, CompoundEntityRef } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { assertError, ForwardedError } from '@backstage/errors';\nimport express from 'express';\nimport JSON5 from 'json5';\nimport limiterFactory from 'p-limit';\nimport { default as path, default as platformPath } from 'node:path';\nimport {\n bulkStorageOperation,\n getCloudPathForLocalPath,\n getFileTreeRecursively,\n getHeadersForFileExtension,\n lowerCaseEntityTriplet,\n getStaleFiles,\n lowerCaseEntityTripletInStoragePath,\n} from './helpers';\nimport {\n PublisherBase,\n PublishRequest,\n PublishResponse,\n ReadinessResponse,\n TechDocsMetadata,\n} from './types';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\n// The number of batches that may be ongoing at the same time.\nconst BATCH_CONCURRENCY = 3;\n\nexport class AzureBlobStoragePublish implements PublisherBase {\n private readonly storageClient: BlobServiceClient;\n private readonly containerName: string;\n private readonly legacyPathCasing: boolean;\n private readonly logger: LoggerService;\n\n constructor(options: {\n storageClient: BlobServiceClient;\n containerName: string;\n legacyPathCasing: boolean;\n logger: LoggerService;\n }) {\n this.storageClient = options.storageClient;\n this.containerName = options.containerName;\n this.legacyPathCasing = options.legacyPathCasing;\n this.logger = options.logger;\n }\n\n static fromConfig(config: Config, logger: LoggerService): PublisherBase {\n let storageClient: BlobServiceClient;\n let containerName = '';\n try {\n containerName = config.getString(\n 'techdocs.publisher.azureBlobStorage.containerName',\n );\n } catch (error) {\n throw new Error(\n \"Since techdocs.publisher.type is set to 'azureBlobStorage' in your app config, \" +\n 'techdocs.publisher.azureBlobStorage.containerName is required.',\n );\n }\n\n const legacyPathCasing =\n config.getOptionalBoolean(\n 'techdocs.legacyUseCaseSensitiveTripletPaths',\n ) || false;\n\n // Give more priority for connectionString, if configured, return the AzureBlobStoragePublish object here itself\n const connectionStringKey =\n 'techdocs.publisher.azureBlobStorage.connectionString';\n const connectionString = config.getOptionalString(connectionStringKey);\n\n if (connectionString) {\n logger.info(\n `Using '${connectionStringKey}' configuration to create storage client`,\n );\n storageClient = BlobServiceClient.fromConnectionString(connectionString);\n } else {\n let accountName = '';\n try {\n accountName = config.getString(\n 'techdocs.publisher.azureBlobStorage.credentials.accountName',\n );\n } catch (error) {\n throw new Error(\n \"Since techdocs.publisher.type is set to 'azureBlobStorage' in your app config, \" +\n 'techdocs.publisher.azureBlobStorage.credentials.accountName is required.',\n );\n }\n\n // Credentials is an optional config. If missing, default Azure Blob Storage environment variables will be used.\n // https://docs.microsoft.com/en-us/azure/storage/common/storage-auth-aad-app\n const accountKey = config.getOptionalString(\n 'techdocs.publisher.azureBlobStorage.credentials.accountKey',\n );\n\n let credential;\n if (accountKey) {\n credential = new StorageSharedKeyCredential(accountName, accountKey);\n } else {\n credential = new DefaultAzureCredential();\n }\n\n storageClient = new BlobServiceClient(\n `https://${accountName}.blob.core.windows.net`,\n credential,\n );\n }\n\n return new AzureBlobStoragePublish({\n storageClient: storageClient,\n containerName: containerName,\n legacyPathCasing: legacyPathCasing,\n logger: logger,\n });\n }\n\n async getReadiness(): Promise<ReadinessResponse> {\n try {\n const response = await this.storageClient\n .getContainerClient(this.containerName)\n .getProperties();\n\n if (response._response.status === 200) {\n return {\n isAvailable: true,\n };\n }\n\n if (response._response.status >= 400) {\n this.logger.error(\n `Failed to retrieve metadata from ${response._response.request.url} with status code ${response._response.status}.`,\n );\n }\n } catch (e) {\n assertError(e);\n this.logger.error(`from Azure Blob Storage client library: ${e.message}`);\n }\n\n this.logger.error(\n `Could not retrieve metadata about the Azure Blob Storage container ${this.containerName}. ` +\n 'Make sure that the Azure project and container exist and the access key is setup correctly ' +\n 'techdocs.publisher.azureBlobStorage.credentials defined in app config has correct permissions. ' +\n 'Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage',\n );\n\n return { isAvailable: false };\n }\n\n /**\n * Upload all the files from the generated `directory` to the Azure Blob Storage container.\n * Directory structure used in the container is - entityNamespace/entityKind/entityName/index.html\n */\n async publish({\n entity,\n directory,\n }: PublishRequest): Promise<PublishResponse> {\n const objects: string[] = [];\n const useLegacyPathCasing = this.legacyPathCasing;\n\n // First, try to retrieve a list of all individual files currently existing\n const remoteFolder = getCloudPathForLocalPath(\n entity,\n undefined,\n useLegacyPathCasing,\n );\n let existingFiles: string[] = [];\n try {\n existingFiles = await this.getAllBlobsFromContainer({\n prefix: remoteFolder,\n maxPageSize: BATCH_CONCURRENCY,\n });\n } catch (e) {\n assertError(e);\n this.logger.error(\n `Unable to list files for Entity ${entity.metadata.name}: ${e.message}`,\n );\n }\n\n // Then, merge new files into the same folder\n let absoluteFilesToUpload;\n let container: ContainerClient;\n try {\n // Remove the absolute path prefix of the source directory\n // Path of all files to upload, relative to the root of the source directory\n // e.g. ['index.html', 'sub-page/index.html', 'assets/images/favicon.png']\n absoluteFilesToUpload = await getFileTreeRecursively(directory);\n\n container = this.storageClient.getContainerClient(this.containerName);\n const failedOperations: Error[] = [];\n await bulkStorageOperation(\n async absoluteFilePath => {\n const relativeFilePath = path.normalize(\n path.relative(directory, absoluteFilePath),\n );\n const remotePath = getCloudPathForLocalPath(\n entity,\n relativeFilePath,\n useLegacyPathCasing,\n );\n objects.push(remotePath);\n const response = await container\n .getBlockBlobClient(remotePath)\n .uploadFile(absoluteFilePath);\n\n if (response._response.status >= 400) {\n failedOperations.push(\n new Error(\n `Upload failed for ${absoluteFilePath} with status code ${response._response.status}`,\n ),\n );\n }\n\n return response;\n },\n absoluteFilesToUpload,\n { concurrencyLimit: BATCH_CONCURRENCY },\n );\n\n if (failedOperations.length > 0) {\n throw new Error(\n failedOperations\n .map(r => r.message)\n .filter(Boolean)\n .join(' '),\n );\n }\n\n this.logger.info(\n `Successfully uploaded all the generated files for Entity ${entity.metadata.name}. Total number of files: ${absoluteFilesToUpload.length}`,\n );\n } catch (e) {\n const errorMessage = `Unable to upload file(s) to Azure. ${e}`;\n this.logger.error(errorMessage);\n throw new Error(errorMessage);\n }\n\n // Last, try to remove the files that were *only* present previously\n try {\n const relativeFilesToUpload = absoluteFilesToUpload.map(\n absoluteFilePath =>\n getCloudPathForLocalPath(\n entity,\n path.relative(directory, absoluteFilePath),\n useLegacyPathCasing,\n ),\n );\n\n const staleFiles = getStaleFiles(relativeFilesToUpload, existingFiles);\n\n await bulkStorageOperation(\n async relativeFilePath => {\n return await container.deleteBlob(relativeFilePath);\n },\n staleFiles,\n { concurrencyLimit: BATCH_CONCURRENCY },\n );\n\n this.logger.info(\n `Successfully deleted stale files for Entity ${entity.metadata.name}. Total number of files: ${staleFiles.length}`,\n );\n } catch (error) {\n const errorMessage = `Unable to delete file(s) from Azure. ${error}`;\n this.logger.error(errorMessage);\n }\n\n return { objects };\n }\n\n async fetchTechDocsMetadata(\n entityName: CompoundEntityRef,\n ): Promise<TechDocsMetadata> {\n const entityTriplet = `${entityName.namespace}/${entityName.kind}/${entityName.name}`;\n const entityRootDir = this.legacyPathCasing\n ? entityTriplet\n : lowerCaseEntityTriplet(entityTriplet);\n\n try {\n const techdocsMetadataJson = await new Promise<Buffer>(\n (resolve, reject) => {\n const fileStreamChunks: Array<any> = [];\n this.storageClient\n .getContainerClient(this.containerName)\n .getBlockBlobClient(`${entityRootDir}/techdocs_metadata.json`)\n .download()\n .then(res => {\n const body = res.readableStreamBody;\n if (!body) {\n reject(new Error(`Unable to parse the response data`));\n return;\n }\n body\n .on('error', reject)\n .on('data', chunk => {\n fileStreamChunks.push(chunk);\n })\n .on('end', () => {\n resolve(Buffer.concat(fileStreamChunks));\n });\n })\n .catch(reject);\n },\n );\n\n if (!techdocsMetadataJson) {\n throw new Error(\n `Unable to parse the techdocs metadata file ${entityRootDir}/techdocs_metadata.json.`,\n );\n }\n const techdocsMetadata = JSON5.parse(\n techdocsMetadataJson.toString('utf-8'),\n );\n return techdocsMetadata;\n } catch (e) {\n throw new ForwardedError('TechDocs metadata fetch failed', e);\n }\n }\n\n /**\n * Express route middleware to serve static files on a route in techdocs-backend.\n */\n docsRouter(): express.Handler {\n return (req, res) => {\n // Decode and trim the leading forward slash\n const decodedUri = decodeURI(req.path.replace(/^\\//, ''));\n\n // filePath example - /default/Component/documented-component/index.html\n const filePath = this.legacyPathCasing\n ? decodedUri\n : lowerCaseEntityTripletInStoragePath(decodedUri);\n\n // Files with different extensions (CSS, HTML) need to be served with different headers\n const fileExtension = platformPath.extname(filePath);\n const responseHeaders = getHeadersForFileExtension(fileExtension);\n\n const blobClient = this.storageClient\n .getContainerClient(this.containerName)\n .getBlockBlobClient(filePath);\n\n blobClient\n .download()\n .then(downloadRes => {\n if (!downloadRes.readableStreamBody) {\n throw new Error('Unable to parse the response data');\n }\n for (const [headerKey, headerValue] of Object.entries(\n responseHeaders,\n )) {\n res.setHeader(headerKey, headerValue);\n }\n downloadRes.readableStreamBody.pipe(res);\n })\n .catch(e => {\n this.logger.warn(\n `TechDocs Azure router failed to serve content from container ${this.containerName} at path ${filePath}: ${e.message}`,\n );\n if (!res.headersSent) {\n res.status(404).send('File Not Found');\n } else {\n res.destroy();\n }\n });\n };\n }\n\n /**\n * A helper function which checks if index.html of an Entity's docs site is available. This\n * can be used to verify if there are any pre-generated docs available to serve.\n */\n hasDocsBeenGenerated(entity: Entity): Promise<boolean> {\n const entityTriplet = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;\n const entityRootDir = this.legacyPathCasing\n ? entityTriplet\n : lowerCaseEntityTriplet(entityTriplet);\n\n return this.storageClient\n .getContainerClient(this.containerName)\n .getBlockBlobClient(`${entityRootDir}/index.html`)\n .exists();\n }\n\n protected async renameBlob(\n originalName: string,\n newName: string,\n removeOriginal = false,\n ): Promise<void> {\n const container = this.storageClient.getContainerClient(this.containerName);\n const blob = container.getBlobClient(newName);\n const { url } = container.getBlobClient(originalName);\n const response = await blob.beginCopyFromURL(url);\n await response.pollUntilDone();\n if (removeOriginal) {\n await container.deleteBlob(originalName);\n }\n }\n\n protected async renameBlobToLowerCase(\n originalPath: string,\n removeOriginal: boolean,\n ) {\n let newPath;\n try {\n newPath = lowerCaseEntityTripletInStoragePath(originalPath);\n } catch (e) {\n assertError(e);\n this.logger.warn(e.message);\n return;\n }\n\n if (originalPath === newPath) return;\n try {\n this.logger.debug(`Migrating ${originalPath}`);\n await this.renameBlob(originalPath, newPath, removeOriginal);\n } catch (e) {\n assertError(e);\n this.logger.warn(`Unable to migrate ${originalPath}: ${e.message}`);\n }\n }\n\n async migrateDocsCase({\n removeOriginal = false,\n concurrency = 25,\n }): Promise<void> {\n const promises = [];\n const limiter = limiterFactory(concurrency);\n const container = this.storageClient.getContainerClient(this.containerName);\n\n for await (const blob of container.listBlobsFlat()) {\n promises.push(\n limiter(\n this.renameBlobToLowerCase.bind(this),\n blob.name,\n removeOriginal,\n ),\n );\n }\n\n await Promise.all(promises);\n }\n\n protected async getAllBlobsFromContainer({\n prefix,\n maxPageSize,\n }: {\n prefix: string;\n maxPageSize: number;\n }): Promise<string[]> {\n const blobs: string[] = [];\n const container = this.storageClient.getContainerClient(this.containerName);\n\n let iterator = container.listBlobsFlat({ prefix }).byPage({ maxPageSize });\n let response = (await iterator.next()).value;\n\n do {\n for (const blob of response?.segment?.blobItems ?? []) {\n blobs.push(blob.name);\n }\n iterator = container\n .listBlobsFlat({ prefix })\n .byPage({ continuationToken: response.continuationToken, maxPageSize });\n response = (await iterator.next()).value;\n } while (response && response.continuationToken);\n\n return blobs;\n }\n}\n"],"names":["BlobServiceClient","StorageSharedKeyCredential","DefaultAzureCredential","assertError","getCloudPathForLocalPath","getFileTreeRecursively","bulkStorageOperation","path","getStaleFiles","lowerCaseEntityTriplet","JSON5","ForwardedError","lowerCaseEntityTripletInStoragePath","platformPath","getHeadersForFileExtension","limiterFactory"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
var errors = require('@backstage/errors');
|
|
4
4
|
var storage = require('@google-cloud/storage');
|
|
5
5
|
var JSON5 = require('json5');
|
|
6
|
-
var path = require('path');
|
|
6
|
+
var path = require('node:path');
|
|
7
7
|
var helpers = require('./helpers.cjs.js');
|
|
8
8
|
var GoogleMigration = require('./migrations/GoogleMigration.cjs.js');
|
|
9
9
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"googleStorage.cjs.js","sources":["../../../src/stages/publish/googleStorage.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Entity, CompoundEntityRef } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { assertError } from '@backstage/errors';\nimport {\n File,\n FileExistsResponse,\n Storage,\n StorageOptions,\n} from '@google-cloud/storage';\nimport express from 'express';\nimport JSON5 from 'json5';\nimport path from 'path';\nimport { Readable } from 'stream';\nimport {\n getFileTreeRecursively,\n getHeadersForFileExtension,\n isValidContentPath,\n lowerCaseEntityTriplet,\n lowerCaseEntityTripletInStoragePath,\n bulkStorageOperation,\n getCloudPathForLocalPath,\n getStaleFiles,\n normalizeExternalStorageRootPath,\n} from './helpers';\nimport { MigrateWriteStream } from './migrations';\nimport {\n PublisherBase,\n PublishRequest,\n PublishResponse,\n ReadinessResponse,\n TechDocsMetadata,\n} from './types';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\nexport class GoogleGCSPublish implements PublisherBase {\n private readonly storageClient: Storage;\n private readonly bucketName: string;\n private readonly legacyPathCasing: boolean;\n private readonly logger: LoggerService;\n private readonly bucketRootPath: string;\n\n constructor(options: {\n storageClient: Storage;\n bucketName: string;\n legacyPathCasing: boolean;\n logger: LoggerService;\n bucketRootPath: string;\n }) {\n this.storageClient = options.storageClient;\n this.bucketName = options.bucketName;\n this.legacyPathCasing = options.legacyPathCasing;\n this.logger = options.logger;\n this.bucketRootPath = options.bucketRootPath;\n }\n\n static fromConfig(\n config: Config,\n logger: LoggerService,\n options?: StorageOptions,\n ): PublisherBase {\n let bucketName = '';\n try {\n bucketName = config.getString('techdocs.publisher.googleGcs.bucketName');\n } catch (error) {\n throw new Error(\n \"Since techdocs.publisher.type is set to 'googleGcs' in your app config, \" +\n 'techdocs.publisher.googleGcs.bucketName is required.',\n );\n }\n\n const bucketRootPath = normalizeExternalStorageRootPath(\n config.getOptionalString('techdocs.publisher.googleGcs.bucketRootPath') ||\n '',\n );\n\n // Credentials is an optional config. If missing, default GCS environment variables will be used.\n // Read more here https://cloud.google.com/docs/authentication/production\n const credentials = config.getOptionalString(\n 'techdocs.publisher.googleGcs.credentials',\n );\n const projectId = config.getOptionalString(\n 'techdocs.publisher.googleGcs.projectId',\n );\n let credentialsJson: any = {};\n if (credentials) {\n try {\n credentialsJson = JSON.parse(credentials);\n } catch (err) {\n throw new Error(\n 'Error in parsing techdocs.publisher.googleGcs.credentials config to JSON.',\n );\n }\n }\n\n const clientOpts: StorageOptions = options ?? {};\n if (projectId) {\n clientOpts.projectId = projectId;\n }\n\n const storageClient = new Storage({\n ...(credentials && {\n projectId: credentialsJson.project_id,\n credentials: credentialsJson,\n }),\n ...clientOpts,\n });\n\n const legacyPathCasing =\n config.getOptionalBoolean(\n 'techdocs.legacyUseCaseSensitiveTripletPaths',\n ) || false;\n\n return new GoogleGCSPublish({\n storageClient,\n bucketName,\n legacyPathCasing,\n logger,\n bucketRootPath,\n });\n }\n\n /**\n * Check if the defined bucket exists. Being able to connect means the configuration is good\n * and the storage client will work.\n */\n async getReadiness(): Promise<ReadinessResponse> {\n try {\n await this.storageClient.bucket(this.bucketName).getMetadata();\n this.logger.info(\n `Successfully connected to the GCS bucket ${this.bucketName}.`,\n );\n\n return {\n isAvailable: true,\n };\n } catch (err) {\n assertError(err);\n this.logger.error(\n `Could not retrieve metadata about the GCS bucket ${this.bucketName}. ` +\n 'Make sure the bucket exists. Also make sure that authentication is setup either by explicitly defining ' +\n 'techdocs.publisher.googleGcs.credentials in app config or by using environment variables. ' +\n 'Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage',\n );\n this.logger.error(`from GCS client library: ${err.message}`);\n\n return { isAvailable: false };\n }\n }\n\n /**\n * Upload all the files from the generated `directory` to the GCS bucket.\n * Directory structure used in the bucket is - entityNamespace/entityKind/entityName/index.html\n */\n async publish({\n entity,\n directory,\n }: PublishRequest): Promise<PublishResponse> {\n const objects: string[] = [];\n const useLegacyPathCasing = this.legacyPathCasing;\n const bucket = this.storageClient.bucket(this.bucketName);\n const bucketRootPath = this.bucketRootPath;\n\n // First, try to retrieve a list of all individual files currently existing\n let existingFiles: string[] = [];\n try {\n const remoteFolder = getCloudPathForLocalPath(\n entity,\n undefined,\n useLegacyPathCasing,\n bucketRootPath,\n );\n existingFiles = await this.getFilesForFolder(remoteFolder);\n } catch (e) {\n assertError(e);\n this.logger.error(\n `Unable to list files for Entity ${entity.metadata.name}: ${e.message}`,\n );\n }\n\n // Then, merge new files into the same folder\n let absoluteFilesToUpload;\n try {\n // Remove the absolute path prefix of the source directory\n // Path of all files to upload, relative to the root of the source directory\n // e.g. ['index.html', 'sub-page/index.html', 'assets/images/favicon.png']\n absoluteFilesToUpload = await getFileTreeRecursively(directory);\n\n await bulkStorageOperation(\n async absoluteFilePath => {\n const relativeFilePath = path.relative(directory, absoluteFilePath);\n const destination = getCloudPathForLocalPath(\n entity,\n relativeFilePath,\n useLegacyPathCasing,\n bucketRootPath,\n );\n objects.push(destination);\n return await bucket.upload(absoluteFilePath, { destination });\n },\n absoluteFilesToUpload,\n { concurrencyLimit: 10 },\n );\n\n this.logger.info(\n `Successfully uploaded all the generated files for Entity ${entity.metadata.name}. Total number of files: ${absoluteFilesToUpload.length}`,\n );\n } catch (e) {\n const errorMessage = `Unable to upload file(s) to Google Cloud Storage. ${e}`;\n this.logger.error(errorMessage);\n throw new Error(errorMessage);\n }\n\n // Last, try to remove the files that were *only* present previously\n try {\n const relativeFilesToUpload = absoluteFilesToUpload.map(\n absoluteFilePath =>\n getCloudPathForLocalPath(\n entity,\n path.relative(directory, absoluteFilePath),\n useLegacyPathCasing,\n bucketRootPath,\n ),\n );\n const staleFiles = getStaleFiles(relativeFilesToUpload, existingFiles);\n\n await bulkStorageOperation(\n async relativeFilePath => {\n return await bucket.file(relativeFilePath).delete();\n },\n staleFiles,\n { concurrencyLimit: 10 },\n );\n\n this.logger.info(\n `Successfully deleted stale files for Entity ${entity.metadata.name}. Total number of files: ${staleFiles.length}`,\n );\n } catch (error) {\n const errorMessage = `Unable to delete file(s) from Google Cloud Storage. ${error}`;\n this.logger.error(errorMessage);\n }\n\n return { objects };\n }\n\n fetchTechDocsMetadata(\n entityName: CompoundEntityRef,\n ): Promise<TechDocsMetadata> {\n return new Promise((resolve, reject) => {\n const entityTriplet = `${entityName.namespace}/${entityName.kind}/${entityName.name}`;\n const entityDir = this.legacyPathCasing\n ? entityTriplet\n : lowerCaseEntityTriplet(entityTriplet);\n\n const entityRootDir = path.posix.join(this.bucketRootPath, entityDir);\n if (!isValidContentPath(this.bucketRootPath, entityRootDir)) {\n this.logger.error(\n `Invalid content path found while fetching TechDocs metadata: ${entityRootDir}`,\n );\n reject(new Error(`Metadata Not Found`));\n }\n\n const fileStreamChunks: Array<any> = [];\n this.storageClient\n .bucket(this.bucketName)\n .file(`${entityRootDir}/techdocs_metadata.json`)\n .createReadStream()\n .on('error', err => {\n this.logger.error(err.message);\n reject(err);\n })\n .on('data', chunk => {\n fileStreamChunks.push(chunk);\n })\n .on('end', () => {\n const techdocsMetadataJson =\n Buffer.concat(fileStreamChunks).toString('utf-8');\n resolve(JSON5.parse(techdocsMetadataJson));\n });\n });\n }\n\n /**\n * Express route middleware to serve static files on a route in techdocs-backend.\n */\n docsRouter(): express.Handler {\n return (req, res) => {\n const decodedUri = decodeURI(req.path.replace(/^\\//, ''));\n\n // filePath example - /default/component/documented-component/index.html\n const filePathNoRoot = this.legacyPathCasing\n ? decodedUri\n : lowerCaseEntityTripletInStoragePath(decodedUri);\n\n // Prepend the root path to the relative file path\n const filePath = path.posix.join(this.bucketRootPath, filePathNoRoot);\n if (!isValidContentPath(this.bucketRootPath, filePath)) {\n this.logger.error(\n `Attempted to fetch TechDocs content for a file outside of the bucket root: ${filePathNoRoot}`,\n );\n res.status(404).send('File Not Found');\n return;\n }\n\n // Files with different extensions (CSS, HTML) need to be served with different headers\n const fileExtension = path.extname(filePath);\n const responseHeaders = getHeadersForFileExtension(fileExtension);\n\n // Pipe file chunks directly from storage to client.\n this.storageClient\n .bucket(this.bucketName)\n .file(filePath)\n .createReadStream()\n .on('pipe', () => {\n res.writeHead(200, responseHeaders);\n })\n .on('error', err => {\n this.logger.warn(\n `TechDocs Google GCS router failed to serve content from bucket ${this.bucketName} at path ${filePath}: ${err.message}`,\n );\n // Send a 404 with a meaningful message if possible.\n if (!res.headersSent) {\n res.status(404).send('File Not Found');\n } else {\n res.destroy();\n }\n })\n .pipe(res);\n };\n }\n\n /**\n * A helper function which checks if index.html of an Entity's docs site is available. This\n * can be used to verify if there are any pre-generated docs available to serve.\n */\n async hasDocsBeenGenerated(entity: Entity): Promise<boolean> {\n return new Promise(resolve => {\n const entityTriplet = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;\n const entityDir = this.legacyPathCasing\n ? entityTriplet\n : lowerCaseEntityTriplet(entityTriplet);\n\n const entityRootDir = path.posix.join(this.bucketRootPath, entityDir);\n if (!isValidContentPath(this.bucketRootPath, entityRootDir)) {\n this.logger.error(\n `Invalid content path found while checking if docs have been generated: ${entityRootDir}`,\n );\n resolve(false);\n }\n\n this.storageClient\n .bucket(this.bucketName)\n .file(`${entityRootDir}/index.html`)\n .exists()\n .then((response: FileExistsResponse) => {\n resolve(response[0]);\n })\n .catch(() => {\n resolve(false);\n });\n });\n }\n\n migrateDocsCase({ removeOriginal = false, concurrency = 25 }): Promise<void> {\n return new Promise((resolve, reject) => {\n // Iterate through every file in the root of the publisher.\n const allFileMetadata: Readable = this.storageClient\n .bucket(this.bucketName)\n .getFilesStream();\n const migrateFiles = new MigrateWriteStream(\n this.logger,\n removeOriginal,\n concurrency,\n );\n migrateFiles.on('finish', resolve).on('error', reject);\n allFileMetadata.pipe(migrateFiles).on('error', error => {\n migrateFiles.destroy();\n reject(error);\n });\n });\n }\n\n private getFilesForFolder(folder: string): Promise<string[]> {\n const fileMetadataStream: Readable = this.storageClient\n .bucket(this.bucketName)\n .getFilesStream({ prefix: folder });\n\n return new Promise((resolve, reject) => {\n const files: string[] = [];\n\n fileMetadataStream.on('error', error => {\n // push file to file array\n reject(error);\n });\n\n fileMetadataStream.on('data', (file: File) => {\n // push file to file array\n files.push(file.name);\n });\n\n fileMetadataStream.on('end', () => {\n // resolve promise\n resolve(files);\n });\n });\n }\n}\n"],"names":["normalizeExternalStorageRootPath","Storage","assertError","getCloudPathForLocalPath","getFileTreeRecursively","bulkStorageOperation","path","getStaleFiles","lowerCaseEntityTriplet","isValidContentPath","JSON5","lowerCaseEntityTripletInStoragePath","getHeadersForFileExtension","MigrateWriteStream"],"mappings":";;;;;;;;;;;;;;AAiDO,MAAM,gBAAA,CAA0C;AAAA,EACpC,aAAA;AAAA,EACA,UAAA;AAAA,EACA,gBAAA;AAAA,EACA,MAAA;AAAA,EACA,cAAA;AAAA,EAEjB,YAAY,OAAA,EAMT;AACD,IAAA,IAAA,CAAK,gBAAgB,OAAA,CAAQ,aAAA;AAC7B,IAAA,IAAA,CAAK,aAAa,OAAA,CAAQ,UAAA;AAC1B,IAAA,IAAA,CAAK,mBAAmB,OAAA,CAAQ,gBAAA;AAChC,IAAA,IAAA,CAAK,SAAS,OAAA,CAAQ,MAAA;AACtB,IAAA,IAAA,CAAK,iBAAiB,OAAA,CAAQ,cAAA;AAAA,EAChC;AAAA,EAEA,OAAO,UAAA,CACL,MAAA,EACA,MAAA,EACA,OAAA,EACe;AACf,IAAA,IAAI,UAAA,GAAa,EAAA;AACjB,IAAA,IAAI;AACF,MAAA,UAAA,GAAa,MAAA,CAAO,UAAU,yCAAyC,CAAA;AAAA,IACzE,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,OAEF;AAAA,IACF;AAEA,IAAA,MAAM,cAAA,GAAiBA,wCAAA;AAAA,MACrB,MAAA,CAAO,iBAAA,CAAkB,6CAA6C,CAAA,IACpE;AAAA,KACJ;AAIA,IAAA,MAAM,cAAc,MAAA,CAAO,iBAAA;AAAA,MACzB;AAAA,KACF;AACA,IAAA,MAAM,YAAY,MAAA,CAAO,iBAAA;AAAA,MACvB;AAAA,KACF;AACA,IAAA,IAAI,kBAAuB,EAAC;AAC5B,IAAA,IAAI,WAAA,EAAa;AACf,MAAA,IAAI;AACF,QAAA,eAAA,GAAkB,IAAA,CAAK,MAAM,WAAW,CAAA;AAAA,MAC1C,SAAS,GAAA,EAAK;AACZ,QAAA,MAAM,IAAI,KAAA;AAAA,UACR;AAAA,SACF;AAAA,MACF;AAAA,IACF;AAEA,IAAA,MAAM,UAAA,GAA6B,WAAW,EAAC;AAC/C,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,UAAA,CAAW,SAAA,GAAY,SAAA;AAAA,IACzB;AAEA,IAAA,MAAM,aAAA,GAAgB,IAAIC,eAAA,CAAQ;AAAA,MAChC,GAAI,WAAA,IAAe;AAAA,QACjB,WAAW,eAAA,CAAgB,UAAA;AAAA,QAC3B,WAAA,EAAa;AAAA,OACf;AAAA,MACA,GAAG;AAAA,KACJ,CAAA;AAED,IAAA,MAAM,mBACJ,MAAA,CAAO,kBAAA;AAAA,MACL;AAAA,KACF,IAAK,KAAA;AAEP,IAAA,OAAO,IAAI,gBAAA,CAAiB;AAAA,MAC1B,aAAA;AAAA,MACA,UAAA;AAAA,MACA,gBAAA;AAAA,MACA,MAAA;AAAA,MACA;AAAA,KACD,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,YAAA,GAA2C;AAC/C,IAAA,IAAI;AACF,MAAA,MAAM,KAAK,aAAA,CAAc,MAAA,CAAO,IAAA,CAAK,UAAU,EAAE,WAAA,EAAY;AAC7D,MAAA,IAAA,CAAK,MAAA,CAAO,IAAA;AAAA,QACV,CAAA,yCAAA,EAA4C,KAAK,UAAU,CAAA,CAAA;AAAA,OAC7D;AAEA,MAAA,OAAO;AAAA,QACL,WAAA,EAAa;AAAA,OACf;AAAA,IACF,SAAS,GAAA,EAAK;AACZ,MAAAC,kBAAA,CAAY,GAAG,CAAA;AACf,MAAA,IAAA,CAAK,MAAA,CAAO,KAAA;AAAA,QACV,CAAA,iDAAA,EAAoD,KAAK,UAAU,CAAA,2QAAA;AAAA,OAIrE;AACA,MAAA,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,CAAA,yBAAA,EAA4B,GAAA,CAAI,OAAO,CAAA,CAAE,CAAA;AAE3D,MAAA,OAAO,EAAE,aAAa,KAAA,EAAM;AAAA,IAC9B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OAAA,CAAQ;AAAA,IACZ,MAAA;AAAA,IACA;AAAA,GACF,EAA6C;AAC3C,IAAA,MAAM,UAAoB,EAAC;AAC3B,IAAA,MAAM,sBAAsB,IAAA,CAAK,gBAAA;AACjC,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,aAAA,CAAc,MAAA,CAAO,KAAK,UAAU,CAAA;AACxD,IAAA,MAAM,iBAAiB,IAAA,CAAK,cAAA;AAG5B,IAAA,IAAI,gBAA0B,EAAC;AAC/B,IAAA,IAAI;AACF,MAAA,MAAM,YAAA,GAAeC,gCAAA;AAAA,QACnB,MAAA;AAAA,QACA,KAAA,CAAA;AAAA,QACA,mBAAA;AAAA,QACA;AAAA,OACF;AACA,MAAA,aAAA,GAAgB,MAAM,IAAA,CAAK,iBAAA,CAAkB,YAAY,CAAA;AAAA,IAC3D,SAAS,CAAA,EAAG;AACV,MAAAD,kBAAA,CAAY,CAAC,CAAA;AACb,MAAA,IAAA,CAAK,MAAA,CAAO,KAAA;AAAA,QACV,mCAAmC,MAAA,CAAO,QAAA,CAAS,IAAI,CAAA,EAAA,EAAK,EAAE,OAAO,CAAA;AAAA,OACvE;AAAA,IACF;AAGA,IAAA,IAAI,qBAAA;AACJ,IAAA,IAAI;AAIF,MAAA,qBAAA,GAAwB,MAAME,+BAAuB,SAAS,CAAA;AAE9D,MAAA,MAAMC,4BAAA;AAAA,QACJ,OAAM,gBAAA,KAAoB;AACxB,UAAA,MAAM,gBAAA,GAAmBC,qBAAA,CAAK,QAAA,CAAS,SAAA,EAAW,gBAAgB,CAAA;AAClE,UAAA,MAAM,WAAA,GAAcH,gCAAA;AAAA,YAClB,MAAA;AAAA,YACA,gBAAA;AAAA,YACA,mBAAA;AAAA,YACA;AAAA,WACF;AACA,UAAA,OAAA,CAAQ,KAAK,WAAW,CAAA;AACxB,UAAA,OAAO,MAAM,MAAA,CAAO,MAAA,CAAO,gBAAA,EAAkB,EAAE,aAAa,CAAA;AAAA,QAC9D,CAAA;AAAA,QACA,qBAAA;AAAA,QACA,EAAE,kBAAkB,EAAA;AAAG,OACzB;AAEA,MAAA,IAAA,CAAK,MAAA,CAAO,IAAA;AAAA,QACV,4DAA4D,MAAA,CAAO,QAAA,CAAS,IAAI,CAAA,yBAAA,EAA4B,sBAAsB,MAAM,CAAA;AAAA,OAC1I;AAAA,IACF,SAAS,CAAA,EAAG;AACV,MAAA,MAAM,YAAA,GAAe,qDAAqD,CAAC,CAAA,CAAA;AAC3E,MAAA,IAAA,CAAK,MAAA,CAAO,MAAM,YAAY,CAAA;AAC9B,MAAA,MAAM,IAAI,MAAM,YAAY,CAAA;AAAA,IAC9B;AAGA,IAAA,IAAI;AACF,MAAA,MAAM,wBAAwB,qBAAA,CAAsB,GAAA;AAAA,QAClD,CAAA,gBAAA,KACEA,gCAAA;AAAA,UACE,MAAA;AAAA,UACAG,qBAAA,CAAK,QAAA,CAAS,SAAA,EAAW,gBAAgB,CAAA;AAAA,UACzC,mBAAA;AAAA,UACA;AAAA;AACF,OACJ;AACA,MAAA,MAAM,UAAA,GAAaC,qBAAA,CAAc,qBAAA,EAAuB,aAAa,CAAA;AAErE,MAAA,MAAMF,4BAAA;AAAA,QACJ,OAAM,gBAAA,KAAoB;AACxB,UAAA,OAAO,MAAM,MAAA,CAAO,IAAA,CAAK,gBAAgB,EAAE,MAAA,EAAO;AAAA,QACpD,CAAA;AAAA,QACA,UAAA;AAAA,QACA,EAAE,kBAAkB,EAAA;AAAG,OACzB;AAEA,MAAA,IAAA,CAAK,MAAA,CAAO,IAAA;AAAA,QACV,+CAA+C,MAAA,CAAO,QAAA,CAAS,IAAI,CAAA,yBAAA,EAA4B,WAAW,MAAM,CAAA;AAAA,OAClH;AAAA,IACF,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,YAAA,GAAe,uDAAuD,KAAK,CAAA,CAAA;AACjF,MAAA,IAAA,CAAK,MAAA,CAAO,MAAM,YAAY,CAAA;AAAA,IAChC;AAEA,IAAA,OAAO,EAAE,OAAA,EAAQ;AAAA,EACnB;AAAA,EAEA,sBACE,UAAA,EAC2B;AAC3B,IAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,MAAA,MAAM,aAAA,GAAgB,GAAG,UAAA,CAAW,SAAS,IAAI,UAAA,CAAW,IAAI,CAAA,CAAA,EAAI,UAAA,CAAW,IAAI,CAAA,CAAA;AACnF,MAAA,MAAM,SAAA,GAAY,IAAA,CAAK,gBAAA,GACnB,aAAA,GACAG,+BAAuB,aAAa,CAAA;AAExC,MAAA,MAAM,gBAAgBF,qBAAA,CAAK,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,gBAAgB,SAAS,CAAA;AACpE,MAAA,IAAI,CAACG,0BAAA,CAAmB,IAAA,CAAK,cAAA,EAAgB,aAAa,CAAA,EAAG;AAC3D,QAAA,IAAA,CAAK,MAAA,CAAO,KAAA;AAAA,UACV,gEAAgE,aAAa,CAAA;AAAA,SAC/E;AACA,QAAA,MAAA,CAAO,IAAI,KAAA,CAAM,CAAA,kBAAA,CAAoB,CAAC,CAAA;AAAA,MACxC;AAEA,MAAA,MAAM,mBAA+B,EAAC;AACtC,MAAA,IAAA,CAAK,aAAA,CACF,MAAA,CAAO,IAAA,CAAK,UAAU,EACtB,IAAA,CAAK,CAAA,EAAG,aAAa,CAAA,uBAAA,CAAyB,CAAA,CAC9C,gBAAA,EAAiB,CACjB,EAAA,CAAG,SAAS,CAAA,GAAA,KAAO;AAClB,QAAA,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,GAAA,CAAI,OAAO,CAAA;AAC7B,QAAA,MAAA,CAAO,GAAG,CAAA;AAAA,MACZ,CAAC,CAAA,CACA,EAAA,CAAG,MAAA,EAAQ,CAAA,KAAA,KAAS;AACnB,QAAA,gBAAA,CAAiB,KAAK,KAAK,CAAA;AAAA,MAC7B,CAAC,CAAA,CACA,EAAA,CAAG,KAAA,EAAO,MAAM;AACf,QAAA,MAAM,uBACJ,MAAA,CAAO,MAAA,CAAO,gBAAgB,CAAA,CAAE,SAAS,OAAO,CAAA;AAClD,QAAA,OAAA,CAAQC,sBAAA,CAAM,KAAA,CAAM,oBAAoB,CAAC,CAAA;AAAA,MAC3C,CAAC,CAAA;AAAA,IACL,CAAC,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,UAAA,GAA8B;AAC5B,IAAA,OAAO,CAAC,KAAK,GAAA,KAAQ;AACnB,MAAA,MAAM,aAAa,SAAA,CAAU,GAAA,CAAI,KAAK,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA;AAGxD,MAAA,MAAM,cAAA,GAAiB,IAAA,CAAK,gBAAA,GACxB,UAAA,GACAC,4CAAoC,UAAU,CAAA;AAGlD,MAAA,MAAM,WAAWL,qBAAA,CAAK,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,gBAAgB,cAAc,CAAA;AACpE,MAAA,IAAI,CAACG,0BAAA,CAAmB,IAAA,CAAK,cAAA,EAAgB,QAAQ,CAAA,EAAG;AACtD,QAAA,IAAA,CAAK,MAAA,CAAO,KAAA;AAAA,UACV,8EAA8E,cAAc,CAAA;AAAA,SAC9F;AACA,QAAA,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK,gBAAgB,CAAA;AACrC,QAAA;AAAA,MACF;AAGA,MAAA,MAAM,aAAA,GAAgBH,qBAAA,CAAK,OAAA,CAAQ,QAAQ,CAAA;AAC3C,MAAA,MAAM,eAAA,GAAkBM,mCAA2B,aAAa,CAAA;AAGhE,MAAA,IAAA,CAAK,aAAA,CACF,MAAA,CAAO,IAAA,CAAK,UAAU,CAAA,CACtB,IAAA,CAAK,QAAQ,CAAA,CACb,gBAAA,EAAiB,CACjB,EAAA,CAAG,MAAA,EAAQ,MAAM;AAChB,QAAA,GAAA,CAAI,SAAA,CAAU,KAAK,eAAe,CAAA;AAAA,MACpC,CAAC,CAAA,CACA,EAAA,CAAG,OAAA,EAAS,CAAA,GAAA,KAAO;AAClB,QAAA,IAAA,CAAK,MAAA,CAAO,IAAA;AAAA,UACV,kEAAkE,IAAA,CAAK,UAAU,YAAY,QAAQ,CAAA,EAAA,EAAK,IAAI,OAAO,CAAA;AAAA,SACvH;AAEA,QAAA,IAAI,CAAC,IAAI,WAAA,EAAa;AACpB,UAAA,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK,gBAAgB,CAAA;AAAA,QACvC,CAAA,MAAO;AACL,UAAA,GAAA,CAAI,OAAA,EAAQ;AAAA,QACd;AAAA,MACF,CAAC,CAAA,CACA,IAAA,CAAK,GAAG,CAAA;AAAA,IACb,CAAA;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,qBAAqB,MAAA,EAAkC;AAC3D,IAAA,OAAO,IAAI,QAAQ,CAAA,OAAA,KAAW;AAC5B,MAAA,MAAM,aAAA,GAAgB,CAAA,EAAG,MAAA,CAAO,QAAA,CAAS,SAAS,CAAA,CAAA,EAAI,MAAA,CAAO,IAAI,CAAA,CAAA,EAAI,MAAA,CAAO,QAAA,CAAS,IAAI,CAAA,CAAA;AACzF,MAAA,MAAM,SAAA,GAAY,IAAA,CAAK,gBAAA,GACnB,aAAA,GACAJ,+BAAuB,aAAa,CAAA;AAExC,MAAA,MAAM,gBAAgBF,qBAAA,CAAK,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,gBAAgB,SAAS,CAAA;AACpE,MAAA,IAAI,CAACG,0BAAA,CAAmB,IAAA,CAAK,cAAA,EAAgB,aAAa,CAAA,EAAG;AAC3D,QAAA,IAAA,CAAK,MAAA,CAAO,KAAA;AAAA,UACV,0EAA0E,aAAa,CAAA;AAAA,SACzF;AACA,QAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,MACf;AAEA,MAAA,IAAA,CAAK,aAAA,CACF,MAAA,CAAO,IAAA,CAAK,UAAU,EACtB,IAAA,CAAK,CAAA,EAAG,aAAa,CAAA,WAAA,CAAa,CAAA,CAClC,MAAA,EAAO,CACP,IAAA,CAAK,CAAC,QAAA,KAAiC;AACtC,QAAA,OAAA,CAAQ,QAAA,CAAS,CAAC,CAAC,CAAA;AAAA,MACrB,CAAC,CAAA,CACA,KAAA,CAAM,MAAM;AACX,QAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,MACf,CAAC,CAAA;AAAA,IACL,CAAC,CAAA;AAAA,EACH;AAAA,EAEA,gBAAgB,EAAE,cAAA,GAAiB,KAAA,EAAO,WAAA,GAAc,IAAG,EAAkB;AAC3E,IAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AAEtC,MAAA,MAAM,kBAA4B,IAAA,CAAK,aAAA,CACpC,OAAO,IAAA,CAAK,UAAU,EACtB,cAAA,EAAe;AAClB,MAAA,MAAM,eAAe,IAAII,kCAAA;AAAA,QACvB,IAAA,CAAK,MAAA;AAAA,QACL,cAAA;AAAA,QACA;AAAA,OACF;AACA,MAAA,YAAA,CAAa,GAAG,QAAA,EAAU,OAAO,CAAA,CAAE,EAAA,CAAG,SAAS,MAAM,CAAA;AACrD,MAAA,eAAA,CAAgB,IAAA,CAAK,YAAY,CAAA,CAAE,EAAA,CAAG,SAAS,CAAA,KAAA,KAAS;AACtD,QAAA,YAAA,CAAa,OAAA,EAAQ;AACrB,QAAA,MAAA,CAAO,KAAK,CAAA;AAAA,MACd,CAAC,CAAA;AAAA,IACH,CAAC,CAAA;AAAA,EACH;AAAA,EAEQ,kBAAkB,MAAA,EAAmC;AAC3D,IAAA,MAAM,kBAAA,GAA+B,IAAA,CAAK,aAAA,CACvC,MAAA,CAAO,IAAA,CAAK,UAAU,CAAA,CACtB,cAAA,CAAe,EAAE,MAAA,EAAQ,MAAA,EAAQ,CAAA;AAEpC,IAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,MAAA,MAAM,QAAkB,EAAC;AAEzB,MAAA,kBAAA,CAAmB,EAAA,CAAG,SAAS,CAAA,KAAA,KAAS;AAEtC,QAAA,MAAA,CAAO,KAAK,CAAA;AAAA,MACd,CAAC,CAAA;AAED,MAAA,kBAAA,CAAmB,EAAA,CAAG,MAAA,EAAQ,CAAC,IAAA,KAAe;AAE5C,QAAA,KAAA,CAAM,IAAA,CAAK,KAAK,IAAI,CAAA;AAAA,MACtB,CAAC,CAAA;AAED,MAAA,kBAAA,CAAmB,EAAA,CAAG,OAAO,MAAM;AAEjC,QAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,MACf,CAAC,CAAA;AAAA,IACH,CAAC,CAAA;AAAA,EACH;AACF;;;;"}
|
|
1
|
+
{"version":3,"file":"googleStorage.cjs.js","sources":["../../../src/stages/publish/googleStorage.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Entity, CompoundEntityRef } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { assertError } from '@backstage/errors';\nimport {\n File,\n FileExistsResponse,\n Storage,\n StorageOptions,\n} from '@google-cloud/storage';\nimport express from 'express';\nimport JSON5 from 'json5';\nimport path from 'node:path';\nimport { Readable } from 'node:stream';\nimport {\n getFileTreeRecursively,\n getHeadersForFileExtension,\n isValidContentPath,\n lowerCaseEntityTriplet,\n lowerCaseEntityTripletInStoragePath,\n bulkStorageOperation,\n getCloudPathForLocalPath,\n getStaleFiles,\n normalizeExternalStorageRootPath,\n} from './helpers';\nimport { MigrateWriteStream } from './migrations';\nimport {\n PublisherBase,\n PublishRequest,\n PublishResponse,\n ReadinessResponse,\n TechDocsMetadata,\n} from './types';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\nexport class GoogleGCSPublish implements PublisherBase {\n private readonly storageClient: Storage;\n private readonly bucketName: string;\n private readonly legacyPathCasing: boolean;\n private readonly logger: LoggerService;\n private readonly bucketRootPath: string;\n\n constructor(options: {\n storageClient: Storage;\n bucketName: string;\n legacyPathCasing: boolean;\n logger: LoggerService;\n bucketRootPath: string;\n }) {\n this.storageClient = options.storageClient;\n this.bucketName = options.bucketName;\n this.legacyPathCasing = options.legacyPathCasing;\n this.logger = options.logger;\n this.bucketRootPath = options.bucketRootPath;\n }\n\n static fromConfig(\n config: Config,\n logger: LoggerService,\n options?: StorageOptions,\n ): PublisherBase {\n let bucketName = '';\n try {\n bucketName = config.getString('techdocs.publisher.googleGcs.bucketName');\n } catch (error) {\n throw new Error(\n \"Since techdocs.publisher.type is set to 'googleGcs' in your app config, \" +\n 'techdocs.publisher.googleGcs.bucketName is required.',\n );\n }\n\n const bucketRootPath = normalizeExternalStorageRootPath(\n config.getOptionalString('techdocs.publisher.googleGcs.bucketRootPath') ||\n '',\n );\n\n // Credentials is an optional config. If missing, default GCS environment variables will be used.\n // Read more here https://cloud.google.com/docs/authentication/production\n const credentials = config.getOptionalString(\n 'techdocs.publisher.googleGcs.credentials',\n );\n const projectId = config.getOptionalString(\n 'techdocs.publisher.googleGcs.projectId',\n );\n let credentialsJson: any = {};\n if (credentials) {\n try {\n credentialsJson = JSON.parse(credentials);\n } catch (err) {\n throw new Error(\n 'Error in parsing techdocs.publisher.googleGcs.credentials config to JSON.',\n );\n }\n }\n\n const clientOpts: StorageOptions = options ?? {};\n if (projectId) {\n clientOpts.projectId = projectId;\n }\n\n const storageClient = new Storage({\n ...(credentials && {\n projectId: credentialsJson.project_id,\n credentials: credentialsJson,\n }),\n ...clientOpts,\n });\n\n const legacyPathCasing =\n config.getOptionalBoolean(\n 'techdocs.legacyUseCaseSensitiveTripletPaths',\n ) || false;\n\n return new GoogleGCSPublish({\n storageClient,\n bucketName,\n legacyPathCasing,\n logger,\n bucketRootPath,\n });\n }\n\n /**\n * Check if the defined bucket exists. Being able to connect means the configuration is good\n * and the storage client will work.\n */\n async getReadiness(): Promise<ReadinessResponse> {\n try {\n await this.storageClient.bucket(this.bucketName).getMetadata();\n this.logger.info(\n `Successfully connected to the GCS bucket ${this.bucketName}.`,\n );\n\n return {\n isAvailable: true,\n };\n } catch (err) {\n assertError(err);\n this.logger.error(\n `Could not retrieve metadata about the GCS bucket ${this.bucketName}. ` +\n 'Make sure the bucket exists. Also make sure that authentication is setup either by explicitly defining ' +\n 'techdocs.publisher.googleGcs.credentials in app config or by using environment variables. ' +\n 'Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage',\n );\n this.logger.error(`from GCS client library: ${err.message}`);\n\n return { isAvailable: false };\n }\n }\n\n /**\n * Upload all the files from the generated `directory` to the GCS bucket.\n * Directory structure used in the bucket is - entityNamespace/entityKind/entityName/index.html\n */\n async publish({\n entity,\n directory,\n }: PublishRequest): Promise<PublishResponse> {\n const objects: string[] = [];\n const useLegacyPathCasing = this.legacyPathCasing;\n const bucket = this.storageClient.bucket(this.bucketName);\n const bucketRootPath = this.bucketRootPath;\n\n // First, try to retrieve a list of all individual files currently existing\n let existingFiles: string[] = [];\n try {\n const remoteFolder = getCloudPathForLocalPath(\n entity,\n undefined,\n useLegacyPathCasing,\n bucketRootPath,\n );\n existingFiles = await this.getFilesForFolder(remoteFolder);\n } catch (e) {\n assertError(e);\n this.logger.error(\n `Unable to list files for Entity ${entity.metadata.name}: ${e.message}`,\n );\n }\n\n // Then, merge new files into the same folder\n let absoluteFilesToUpload;\n try {\n // Remove the absolute path prefix of the source directory\n // Path of all files to upload, relative to the root of the source directory\n // e.g. ['index.html', 'sub-page/index.html', 'assets/images/favicon.png']\n absoluteFilesToUpload = await getFileTreeRecursively(directory);\n\n await bulkStorageOperation(\n async absoluteFilePath => {\n const relativeFilePath = path.relative(directory, absoluteFilePath);\n const destination = getCloudPathForLocalPath(\n entity,\n relativeFilePath,\n useLegacyPathCasing,\n bucketRootPath,\n );\n objects.push(destination);\n return await bucket.upload(absoluteFilePath, { destination });\n },\n absoluteFilesToUpload,\n { concurrencyLimit: 10 },\n );\n\n this.logger.info(\n `Successfully uploaded all the generated files for Entity ${entity.metadata.name}. Total number of files: ${absoluteFilesToUpload.length}`,\n );\n } catch (e) {\n const errorMessage = `Unable to upload file(s) to Google Cloud Storage. ${e}`;\n this.logger.error(errorMessage);\n throw new Error(errorMessage);\n }\n\n // Last, try to remove the files that were *only* present previously\n try {\n const relativeFilesToUpload = absoluteFilesToUpload.map(\n absoluteFilePath =>\n getCloudPathForLocalPath(\n entity,\n path.relative(directory, absoluteFilePath),\n useLegacyPathCasing,\n bucketRootPath,\n ),\n );\n const staleFiles = getStaleFiles(relativeFilesToUpload, existingFiles);\n\n await bulkStorageOperation(\n async relativeFilePath => {\n return await bucket.file(relativeFilePath).delete();\n },\n staleFiles,\n { concurrencyLimit: 10 },\n );\n\n this.logger.info(\n `Successfully deleted stale files for Entity ${entity.metadata.name}. Total number of files: ${staleFiles.length}`,\n );\n } catch (error) {\n const errorMessage = `Unable to delete file(s) from Google Cloud Storage. ${error}`;\n this.logger.error(errorMessage);\n }\n\n return { objects };\n }\n\n fetchTechDocsMetadata(\n entityName: CompoundEntityRef,\n ): Promise<TechDocsMetadata> {\n return new Promise((resolve, reject) => {\n const entityTriplet = `${entityName.namespace}/${entityName.kind}/${entityName.name}`;\n const entityDir = this.legacyPathCasing\n ? entityTriplet\n : lowerCaseEntityTriplet(entityTriplet);\n\n const entityRootDir = path.posix.join(this.bucketRootPath, entityDir);\n if (!isValidContentPath(this.bucketRootPath, entityRootDir)) {\n this.logger.error(\n `Invalid content path found while fetching TechDocs metadata: ${entityRootDir}`,\n );\n reject(new Error(`Metadata Not Found`));\n }\n\n const fileStreamChunks: Array<any> = [];\n this.storageClient\n .bucket(this.bucketName)\n .file(`${entityRootDir}/techdocs_metadata.json`)\n .createReadStream()\n .on('error', err => {\n this.logger.error(err.message);\n reject(err);\n })\n .on('data', chunk => {\n fileStreamChunks.push(chunk);\n })\n .on('end', () => {\n const techdocsMetadataJson =\n Buffer.concat(fileStreamChunks).toString('utf-8');\n resolve(JSON5.parse(techdocsMetadataJson));\n });\n });\n }\n\n /**\n * Express route middleware to serve static files on a route in techdocs-backend.\n */\n docsRouter(): express.Handler {\n return (req, res) => {\n const decodedUri = decodeURI(req.path.replace(/^\\//, ''));\n\n // filePath example - /default/component/documented-component/index.html\n const filePathNoRoot = this.legacyPathCasing\n ? decodedUri\n : lowerCaseEntityTripletInStoragePath(decodedUri);\n\n // Prepend the root path to the relative file path\n const filePath = path.posix.join(this.bucketRootPath, filePathNoRoot);\n if (!isValidContentPath(this.bucketRootPath, filePath)) {\n this.logger.error(\n `Attempted to fetch TechDocs content for a file outside of the bucket root: ${filePathNoRoot}`,\n );\n res.status(404).send('File Not Found');\n return;\n }\n\n // Files with different extensions (CSS, HTML) need to be served with different headers\n const fileExtension = path.extname(filePath);\n const responseHeaders = getHeadersForFileExtension(fileExtension);\n\n // Pipe file chunks directly from storage to client.\n this.storageClient\n .bucket(this.bucketName)\n .file(filePath)\n .createReadStream()\n .on('pipe', () => {\n res.writeHead(200, responseHeaders);\n })\n .on('error', err => {\n this.logger.warn(\n `TechDocs Google GCS router failed to serve content from bucket ${this.bucketName} at path ${filePath}: ${err.message}`,\n );\n // Send a 404 with a meaningful message if possible.\n if (!res.headersSent) {\n res.status(404).send('File Not Found');\n } else {\n res.destroy();\n }\n })\n .pipe(res);\n };\n }\n\n /**\n * A helper function which checks if index.html of an Entity's docs site is available. This\n * can be used to verify if there are any pre-generated docs available to serve.\n */\n async hasDocsBeenGenerated(entity: Entity): Promise<boolean> {\n return new Promise(resolve => {\n const entityTriplet = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;\n const entityDir = this.legacyPathCasing\n ? entityTriplet\n : lowerCaseEntityTriplet(entityTriplet);\n\n const entityRootDir = path.posix.join(this.bucketRootPath, entityDir);\n if (!isValidContentPath(this.bucketRootPath, entityRootDir)) {\n this.logger.error(\n `Invalid content path found while checking if docs have been generated: ${entityRootDir}`,\n );\n resolve(false);\n }\n\n this.storageClient\n .bucket(this.bucketName)\n .file(`${entityRootDir}/index.html`)\n .exists()\n .then((response: FileExistsResponse) => {\n resolve(response[0]);\n })\n .catch(() => {\n resolve(false);\n });\n });\n }\n\n migrateDocsCase({ removeOriginal = false, concurrency = 25 }): Promise<void> {\n return new Promise((resolve, reject) => {\n // Iterate through every file in the root of the publisher.\n const allFileMetadata: Readable = this.storageClient\n .bucket(this.bucketName)\n .getFilesStream();\n const migrateFiles = new MigrateWriteStream(\n this.logger,\n removeOriginal,\n concurrency,\n );\n migrateFiles.on('finish', resolve).on('error', reject);\n allFileMetadata.pipe(migrateFiles).on('error', error => {\n migrateFiles.destroy();\n reject(error);\n });\n });\n }\n\n private getFilesForFolder(folder: string): Promise<string[]> {\n const fileMetadataStream: Readable = this.storageClient\n .bucket(this.bucketName)\n .getFilesStream({ prefix: folder });\n\n return new Promise((resolve, reject) => {\n const files: string[] = [];\n\n fileMetadataStream.on('error', error => {\n // push file to file array\n reject(error);\n });\n\n fileMetadataStream.on('data', (file: File) => {\n // push file to file array\n files.push(file.name);\n });\n\n fileMetadataStream.on('end', () => {\n // resolve promise\n resolve(files);\n });\n });\n }\n}\n"],"names":["normalizeExternalStorageRootPath","Storage","assertError","getCloudPathForLocalPath","getFileTreeRecursively","bulkStorageOperation","path","getStaleFiles","lowerCaseEntityTriplet","isValidContentPath","JSON5","lowerCaseEntityTripletInStoragePath","getHeadersForFileExtension","MigrateWriteStream"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.cjs.js","sources":["../../../src/stages/publish/helpers.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Entity, DEFAULT_NAMESPACE } from '@backstage/catalog-model';\nimport mime from 'mime-types';\nimport path from 'path';\nimport createLimiter from 'p-limit';\nimport recursiveReadDir from 'recursive-readdir';\n\n/**\n * Helper to get the expected content-type for a given file extension. Also\n * takes XSS mitigation into account.\n */\nconst getContentTypeForExtension = (ext: string): string => {\n const defaultContentType = 'text/plain; charset=utf-8';\n const excludedTypes = [\n 'text/html',\n 'text/xml',\n 'image/svg+xml',\n 'text/xsl',\n 'application/vnd.wap.xhtml+xml',\n 'multipart/x-mixed-replace',\n 'text/rdf',\n 'application/mathml+xml',\n 'application/octet-stream',\n 'application/rdf+xml',\n 'application/xhtml+xml',\n 'application/xml',\n 'text/cache-manifest',\n 'text/vtt',\n ];\n\n // Prevent sanitization bypass by preventing browsers from directly rendering\n // the contents of untrusted files.\n if (\n ext.match(\n /htm|xml|svg|appcache|manifest|mathml|owl|rdf|rng|vtt|xht|xsd|xsl/i,\n )\n ) {\n return defaultContentType;\n }\n\n // Check again to make sure that the content type is not in the excluded mime-type list\n // We use .lookup here to avoid the \"; charset=...\" addition\n const contentType = mime.lookup(ext);\n if (contentType && excludedTypes.includes(contentType)) {\n return defaultContentType;\n }\n\n return mime.contentType(ext) || defaultContentType;\n};\n\nexport type responseHeadersType = {\n 'Content-Type': string;\n};\n\n/**\n * Some files need special headers to be used correctly by the frontend. This function\n * generates headers in the response to those file requests.\n * @param fileExtension - .html, .css, .js, .png etc.\n */\nexport const getHeadersForFileExtension = (\n fileExtension: string,\n): responseHeadersType => {\n return {\n 'Content-Type': getContentTypeForExtension(fileExtension),\n };\n};\n\n/**\n * Recursively traverse all the sub-directories of a path and return\n * a list of absolute paths of all the files. e.g. tree command in Unix\n *\n * @example\n *\n * /User/username/my_dir\n * dirA\n * | subDirA\n * | | file1\n * EmptyDir\n * dirB\n * | file2\n * file3\n *\n * getFileListRecursively('/Users/username/myDir')\n * // returns\n * [\n * '/User/username/my_dir/dirA/subDirA/file1',\n * '/User/username/my_dir/dirB/file2',\n * '/User/username/my_dir/file3'\n * ]\n * @param rootDirPath - Absolute path to the root directory.\n */\nexport const getFileTreeRecursively = async (\n rootDirPath: string,\n): Promise<string[]> => {\n // Iterate on all the files in the directory and its sub-directories\n const fileList = await recursiveReadDir(rootDirPath).catch(error => {\n throw new Error(`Failed to read template directory: ${error.message}`);\n });\n return fileList;\n};\n\n/**\n * Takes a posix path and returns a lower-cased version of entity's triplet\n * with the remaining path in posix.\n *\n * Path must not include a starting slash.\n *\n * @example\n * lowerCaseEntityTriplet('default/Component/backstage')\n * // return default/component/backstage\n */\nexport const lowerCaseEntityTriplet = (posixPath: string): string => {\n const [namespace, kind, name, ...rest] = posixPath.split(path.posix.sep);\n const lowerNamespace = namespace.toLowerCase();\n const lowerKind = kind.toLowerCase();\n const lowerName = name.toLowerCase();\n return [lowerNamespace, lowerKind, lowerName, ...rest].join(path.posix.sep);\n};\n\n/**\n * Takes either a win32 or posix path and returns a lower-cased version of entity's triplet\n * with the remaining path in posix.\n *\n * Starting slashes will be trimmed.\n *\n * Throws an error if the path does not appear to be an entity triplet.\n *\n * @example\n * lowerCaseEntityTripletInStoragePath('/default/Component/backstage/file.txt')\n * // return default/component/backstage/file.txt\n */\nexport const lowerCaseEntityTripletInStoragePath = (\n originalPath: string,\n): string => {\n let posixPath = originalPath;\n if (originalPath.includes(path.win32.sep)) {\n posixPath = originalPath.split(path.win32.sep).join(path.posix.sep);\n }\n\n // remove leading slash\n const parts = posixPath.split(path.posix.sep);\n if (parts[0] === '') {\n parts.shift();\n }\n\n // check if all parts of the entity exist (name, namespace, kind) plus filename\n if (parts.length <= 3) {\n throw new Error(\n `Encountered file unmanaged by TechDocs ${originalPath}. Skipping.`,\n );\n }\n\n return lowerCaseEntityTriplet(parts.join(path.posix.sep));\n};\n\n/**\n * Take a posix path and return a path without leading and trailing\n * separators\n *\n * @example\n * normalizeExternalStorageRootPath('/backstage-data/techdocs/')\n * // return backstage-data/techdocs\n */\nexport const normalizeExternalStorageRootPath = (posixPath: string): string => {\n // remove leading slash\n let normalizedPath = posixPath;\n if (posixPath.startsWith(path.posix.sep)) {\n normalizedPath = posixPath.slice(1);\n }\n\n // remove trailing slash\n if (normalizedPath.endsWith(path.posix.sep)) {\n normalizedPath = normalizedPath.slice(0, normalizedPath.length - 1);\n }\n\n return normalizedPath;\n};\n\n// Only returns the files that existed previously and are not present anymore.\nexport const getStaleFiles = (\n newFiles: string[],\n oldFiles: string[],\n): string[] => {\n const staleFiles = new Set(oldFiles);\n const removedParentDirs = new Set();\n newFiles.forEach(newFile => {\n staleFiles.delete(newFile);\n\n // We have to traverse through the directory hierarchy of a new file and\n // ensure that we won't try to delete one of the parent directories.\n let parentDir = newFile.substring(0, newFile.lastIndexOf('/'));\n while (\n !removedParentDirs.has(parentDir) &&\n parentDir.length >= newFile.indexOf('/')\n ) {\n staleFiles.delete(parentDir);\n removedParentDirs.add(parentDir);\n parentDir = parentDir.substring(0, parentDir.lastIndexOf('/'));\n }\n });\n return Array.from(staleFiles);\n};\n\n// Compose actual filename on remote bucket including entity information\nexport const getCloudPathForLocalPath = (\n entity: Entity,\n localPath = '',\n useLegacyPathCasing = false,\n externalStorageRootPath = '',\n): string => {\n // Convert destination file path to a POSIX path for uploading.\n // GCS expects / as path separator and relativeFilePath will contain \\\\ on Windows.\n // https://cloud.google.com/storage/docs/gsutil/addlhelp/HowSubdirectoriesWork\n const relativeFilePathPosix = localPath.split(path.sep).join(path.posix.sep);\n\n // The / delimiter is intentional since it represents the cloud storage and not the local file system.\n const entityRootDir = `${entity.metadata?.namespace ?? DEFAULT_NAMESPACE}/${\n entity.kind\n }/${entity.metadata.name}`;\n\n const relativeFilePathTriplet = `${entityRootDir}/${relativeFilePathPosix}`;\n\n const destination = useLegacyPathCasing\n ? relativeFilePathTriplet\n : lowerCaseEntityTriplet(relativeFilePathTriplet);\n\n // Again, the / delimiter is intentional, as it represents remote storage.\n const destinationWithRoot = [\n // The extra filter prevents unintended double slashes and prefixes.\n ...externalStorageRootPath.split(path.posix.sep).filter(s => s !== ''),\n destination,\n ].join('/');\n\n return destinationWithRoot; // Remote storage file relative path\n};\n\n// Perform rate limited generic operations by passing a function and a list of arguments\nexport const bulkStorageOperation = async <T>(\n operation: (arg: T) => Promise<unknown>,\n args: T[],\n { concurrencyLimit } = { concurrencyLimit: 25 },\n) => {\n const limiter = createLimiter(concurrencyLimit);\n await Promise.all(args.map(arg => limiter(operation, arg)));\n};\n\n// Checks content path is the same as or a child path of bucketRoot, specifically for posix paths.\nexport const isValidContentPath = (\n bucketRoot: string,\n contentPath: string,\n): boolean => {\n const relativePath = path.posix.relative(bucketRoot, contentPath);\n if (relativePath === '') {\n // The same directory\n return true;\n }\n\n const outsideBase = relativePath.startsWith('..'); // not outside base\n const differentDrive = path.posix.isAbsolute(relativePath); // on Windows, this means dir is on a different drive from base.\n\n return !outsideBase && !differentDrive;\n};\n"],"names":["mime","recursiveReadDir","path","DEFAULT_NAMESPACE","createLimiter"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"helpers.cjs.js","sources":["../../../src/stages/publish/helpers.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Entity, DEFAULT_NAMESPACE } from '@backstage/catalog-model';\nimport mime from 'mime-types';\nimport path from 'node:path';\nimport createLimiter from 'p-limit';\nimport recursiveReadDir from 'recursive-readdir';\n\n/**\n * Helper to get the expected content-type for a given file extension. Also\n * takes XSS mitigation into account.\n */\nconst getContentTypeForExtension = (ext: string): string => {\n const defaultContentType = 'text/plain; charset=utf-8';\n const excludedTypes = [\n 'text/html',\n 'text/xml',\n 'image/svg+xml',\n 'text/xsl',\n 'application/vnd.wap.xhtml+xml',\n 'multipart/x-mixed-replace',\n 'text/rdf',\n 'application/mathml+xml',\n 'application/octet-stream',\n 'application/rdf+xml',\n 'application/xhtml+xml',\n 'application/xml',\n 'text/cache-manifest',\n 'text/vtt',\n ];\n\n // Prevent sanitization bypass by preventing browsers from directly rendering\n // the contents of untrusted files.\n if (\n ext.match(\n /htm|xml|svg|appcache|manifest|mathml|owl|rdf|rng|vtt|xht|xsd|xsl/i,\n )\n ) {\n return defaultContentType;\n }\n\n // Check again to make sure that the content type is not in the excluded mime-type list\n // We use .lookup here to avoid the \"; charset=...\" addition\n const contentType = mime.lookup(ext);\n if (contentType && excludedTypes.includes(contentType)) {\n return defaultContentType;\n }\n\n return mime.contentType(ext) || defaultContentType;\n};\n\nexport type responseHeadersType = {\n 'Content-Type': string;\n};\n\n/**\n * Some files need special headers to be used correctly by the frontend. This function\n * generates headers in the response to those file requests.\n * @param fileExtension - .html, .css, .js, .png etc.\n */\nexport const getHeadersForFileExtension = (\n fileExtension: string,\n): responseHeadersType => {\n return {\n 'Content-Type': getContentTypeForExtension(fileExtension),\n };\n};\n\n/**\n * Recursively traverse all the sub-directories of a path and return\n * a list of absolute paths of all the files. e.g. tree command in Unix\n *\n * @example\n *\n * /User/username/my_dir\n * dirA\n * | subDirA\n * | | file1\n * EmptyDir\n * dirB\n * | file2\n * file3\n *\n * getFileListRecursively('/Users/username/myDir')\n * // returns\n * [\n * '/User/username/my_dir/dirA/subDirA/file1',\n * '/User/username/my_dir/dirB/file2',\n * '/User/username/my_dir/file3'\n * ]\n * @param rootDirPath - Absolute path to the root directory.\n */\nexport const getFileTreeRecursively = async (\n rootDirPath: string,\n): Promise<string[]> => {\n // Iterate on all the files in the directory and its sub-directories\n const fileList = await recursiveReadDir(rootDirPath).catch(error => {\n throw new Error(`Failed to read template directory: ${error.message}`);\n });\n return fileList;\n};\n\n/**\n * Takes a posix path and returns a lower-cased version of entity's triplet\n * with the remaining path in posix.\n *\n * Path must not include a starting slash.\n *\n * @example\n * lowerCaseEntityTriplet('default/Component/backstage')\n * // return default/component/backstage\n */\nexport const lowerCaseEntityTriplet = (posixPath: string): string => {\n const [namespace, kind, name, ...rest] = posixPath.split(path.posix.sep);\n const lowerNamespace = namespace.toLowerCase();\n const lowerKind = kind.toLowerCase();\n const lowerName = name.toLowerCase();\n return [lowerNamespace, lowerKind, lowerName, ...rest].join(path.posix.sep);\n};\n\n/**\n * Takes either a win32 or posix path and returns a lower-cased version of entity's triplet\n * with the remaining path in posix.\n *\n * Starting slashes will be trimmed.\n *\n * Throws an error if the path does not appear to be an entity triplet.\n *\n * @example\n * lowerCaseEntityTripletInStoragePath('/default/Component/backstage/file.txt')\n * // return default/component/backstage/file.txt\n */\nexport const lowerCaseEntityTripletInStoragePath = (\n originalPath: string,\n): string => {\n let posixPath = originalPath;\n if (originalPath.includes(path.win32.sep)) {\n posixPath = originalPath.split(path.win32.sep).join(path.posix.sep);\n }\n\n // remove leading slash\n const parts = posixPath.split(path.posix.sep);\n if (parts[0] === '') {\n parts.shift();\n }\n\n // check if all parts of the entity exist (name, namespace, kind) plus filename\n if (parts.length <= 3) {\n throw new Error(\n `Encountered file unmanaged by TechDocs ${originalPath}. Skipping.`,\n );\n }\n\n return lowerCaseEntityTriplet(parts.join(path.posix.sep));\n};\n\n/**\n * Take a posix path and return a path without leading and trailing\n * separators\n *\n * @example\n * normalizeExternalStorageRootPath('/backstage-data/techdocs/')\n * // return backstage-data/techdocs\n */\nexport const normalizeExternalStorageRootPath = (posixPath: string): string => {\n // remove leading slash\n let normalizedPath = posixPath;\n if (posixPath.startsWith(path.posix.sep)) {\n normalizedPath = posixPath.slice(1);\n }\n\n // remove trailing slash\n if (normalizedPath.endsWith(path.posix.sep)) {\n normalizedPath = normalizedPath.slice(0, normalizedPath.length - 1);\n }\n\n return normalizedPath;\n};\n\n// Only returns the files that existed previously and are not present anymore.\nexport const getStaleFiles = (\n newFiles: string[],\n oldFiles: string[],\n): string[] => {\n const staleFiles = new Set(oldFiles);\n const removedParentDirs = new Set();\n newFiles.forEach(newFile => {\n staleFiles.delete(newFile);\n\n // We have to traverse through the directory hierarchy of a new file and\n // ensure that we won't try to delete one of the parent directories.\n let parentDir = newFile.substring(0, newFile.lastIndexOf('/'));\n while (\n !removedParentDirs.has(parentDir) &&\n parentDir.length >= newFile.indexOf('/')\n ) {\n staleFiles.delete(parentDir);\n removedParentDirs.add(parentDir);\n parentDir = parentDir.substring(0, parentDir.lastIndexOf('/'));\n }\n });\n return Array.from(staleFiles);\n};\n\n// Compose actual filename on remote bucket including entity information\nexport const getCloudPathForLocalPath = (\n entity: Entity,\n localPath = '',\n useLegacyPathCasing = false,\n externalStorageRootPath = '',\n): string => {\n // Convert destination file path to a POSIX path for uploading.\n // GCS expects / as path separator and relativeFilePath will contain \\\\ on Windows.\n // https://cloud.google.com/storage/docs/gsutil/addlhelp/HowSubdirectoriesWork\n const relativeFilePathPosix = localPath.split(path.sep).join(path.posix.sep);\n\n // The / delimiter is intentional since it represents the cloud storage and not the local file system.\n const entityRootDir = `${entity.metadata?.namespace ?? DEFAULT_NAMESPACE}/${\n entity.kind\n }/${entity.metadata.name}`;\n\n const relativeFilePathTriplet = `${entityRootDir}/${relativeFilePathPosix}`;\n\n const destination = useLegacyPathCasing\n ? relativeFilePathTriplet\n : lowerCaseEntityTriplet(relativeFilePathTriplet);\n\n // Again, the / delimiter is intentional, as it represents remote storage.\n const destinationWithRoot = [\n // The extra filter prevents unintended double slashes and prefixes.\n ...externalStorageRootPath.split(path.posix.sep).filter(s => s !== ''),\n destination,\n ].join('/');\n\n return destinationWithRoot; // Remote storage file relative path\n};\n\n// Perform rate limited generic operations by passing a function and a list of arguments\nexport const bulkStorageOperation = async <T>(\n operation: (arg: T) => Promise<unknown>,\n args: T[],\n { concurrencyLimit } = { concurrencyLimit: 25 },\n) => {\n const limiter = createLimiter(concurrencyLimit);\n await Promise.all(args.map(arg => limiter(operation, arg)));\n};\n\n// Checks content path is the same as or a child path of bucketRoot, specifically for posix paths.\nexport const isValidContentPath = (\n bucketRoot: string,\n contentPath: string,\n): boolean => {\n const relativePath = path.posix.relative(bucketRoot, contentPath);\n if (relativePath === '') {\n // The same directory\n return true;\n }\n\n const outsideBase = relativePath.startsWith('..'); // not outside base\n const differentDrive = path.posix.isAbsolute(relativePath); // on Windows, this means dir is on a different drive from base.\n\n return !outsideBase && !differentDrive;\n};\n"],"names":["mime","recursiveReadDir","path","DEFAULT_NAMESPACE","createLimiter"],"mappings":"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"}
|