TechDocs: AWS Publisher - fix upload path to be OS agnostic
This commit is contained in:
@@ -15,7 +15,28 @@
|
||||
*/
|
||||
import type { S3 as S3Types } from 'aws-sdk';
|
||||
import { EventEmitter } from 'events';
|
||||
import fs from 'fs';
|
||||
import fs from 'fs-extra';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir';
|
||||
|
||||
/**
|
||||
* @param Key Contains either / or \ as file separator depending upon OS.
|
||||
*/
|
||||
const checkFileExists = async (Key: string): Promise<boolean> => {
|
||||
// Key will always have / as file separator irrespective of OS since S3 expects /.
|
||||
// Normalize Key to OS specific path before checking if file exists.
|
||||
const relativeFilePath = Key.split(path.posix.sep).join(path.sep);
|
||||
const filePath = path.join(rootDir, Key);
|
||||
|
||||
try {
|
||||
await fs.access(filePath, fs.constants.F_OK);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export class S3 {
|
||||
private readonly options;
|
||||
@@ -26,22 +47,27 @@ export class S3 {
|
||||
|
||||
headObject({ Key }: { Key: string }) {
|
||||
return {
|
||||
promise: () => this.checkFileExists(Key),
|
||||
promise: async () => {
|
||||
if (!(await checkFileExists(Key))) {
|
||||
throw new Error('File does not exist');
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getObject({ Key }: { Key: string }) {
|
||||
const filePath = path.join(rootDir, Key);
|
||||
return {
|
||||
promise: () => this.checkFileExists(Key),
|
||||
promise: () => checkFileExists(filePath),
|
||||
createReadStream: () => {
|
||||
const emitter = new EventEmitter();
|
||||
process.nextTick(() => {
|
||||
if (fs.existsSync(Key)) {
|
||||
emitter.emit('data', Buffer.from(fs.readFileSync(Key)));
|
||||
if (fs.existsSync(filePath)) {
|
||||
emitter.emit('data', Buffer.from(fs.readFileSync(filePath)));
|
||||
} else {
|
||||
emitter.emit(
|
||||
'error',
|
||||
new Error(`The file ${Key} does not exist !`),
|
||||
new Error(`The file ${filePath} does not exist !`),
|
||||
);
|
||||
}
|
||||
emitter.emit('end');
|
||||
@@ -51,16 +77,6 @@ export class S3 {
|
||||
};
|
||||
}
|
||||
|
||||
checkFileExists(Key: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (fs.existsSync(Key)) {
|
||||
resolve('');
|
||||
} else {
|
||||
reject({ message: 'The object does not exist !' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
headBucket() {
|
||||
return new Promise(resolve => {
|
||||
resolve('');
|
||||
@@ -70,9 +86,9 @@ export class S3 {
|
||||
upload({ Key }: { Key: string }) {
|
||||
return {
|
||||
promise: () =>
|
||||
new Promise((resolve, reject) => {
|
||||
if (!fs.existsSync(Key)) {
|
||||
reject('');
|
||||
new Promise(async (resolve, reject) => {
|
||||
if (!(await checkFileExists(Key))) {
|
||||
reject(`The file ${Key} does not exist`);
|
||||
} else {
|
||||
resolve('');
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
import type { Entity, EntityName } from '@backstage/catalog-model';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import mockFs from 'mock-fs';
|
||||
import path from 'path';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as winston from 'winston';
|
||||
import { AwsS3Publish } from './awsS3';
|
||||
import { PublisherBase, TechDocsMetadata } from './types';
|
||||
@@ -41,13 +42,15 @@ const createMockEntityName = (): EntityName => ({
|
||||
namespace: 'test-namespace',
|
||||
});
|
||||
|
||||
const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir';
|
||||
|
||||
const getEntityRootDir = (entity: Entity) => {
|
||||
const {
|
||||
kind,
|
||||
metadata: { namespace, name },
|
||||
} = entity;
|
||||
const entityRootDir = path.join(namespace as string, kind, name);
|
||||
return entityRootDir;
|
||||
|
||||
return path.join(rootDir, namespace as string, kind, name);
|
||||
};
|
||||
|
||||
const logger = winston.createLogger();
|
||||
@@ -57,6 +60,7 @@ jest.spyOn(logger, 'error').mockReturnValue(logger);
|
||||
let publisher: PublisherBase;
|
||||
|
||||
beforeEach(() => {
|
||||
mockFs.restore();
|
||||
const mockConfig = new ConfigReader({
|
||||
techdocs: {
|
||||
requestUrl: 'http://localhost:7000',
|
||||
@@ -78,6 +82,10 @@ beforeEach(() => {
|
||||
|
||||
describe('AwsS3Publish', () => {
|
||||
describe('publish', () => {
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it('should publish a directory', async () => {
|
||||
const entity = createMockEntity();
|
||||
const entityRootDir = getEntityRootDir(entity);
|
||||
@@ -98,11 +106,11 @@ describe('AwsS3Publish', () => {
|
||||
directory: entityRootDir,
|
||||
}),
|
||||
).toBeUndefined();
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it('should fail to publish a directory', async () => {
|
||||
const wrongPathToGeneratedDirectory = path.join(
|
||||
rootDir,
|
||||
'wrong',
|
||||
'path',
|
||||
'to',
|
||||
@@ -126,13 +134,18 @@ describe('AwsS3Publish', () => {
|
||||
entity,
|
||||
directory: wrongPathToGeneratedDirectory,
|
||||
})
|
||||
.catch(error =>
|
||||
expect(error).toEqual(
|
||||
new Error(
|
||||
`Unable to upload file(s) to AWS S3. Error Failed to read template directory: ENOENT, no such file or directory '${wrongPathToGeneratedDirectory}'`,
|
||||
.catch(error => {
|
||||
expect(error.message).toEqual(
|
||||
// Can not do exact error message match due to mockFs adding unexpected characters in the path when throwing the error
|
||||
// Issue reported https://github.com/tschaub/mock-fs/issues/118
|
||||
expect.stringContaining(
|
||||
`Unable to upload file(s) to AWS S3. Error Failed to read template directory: ENOENT, no such file or directory`,
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
expect(error.message).toEqual(
|
||||
expect.stringContaining(wrongPathToGeneratedDirectory),
|
||||
);
|
||||
});
|
||||
mockFs.restore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -140,9 +140,17 @@ export class AwsS3Publish implements PublisherBase {
|
||||
// Remove the absolute path prefix of the source directory
|
||||
// Path of all files to upload, relative to the root of the source directory
|
||||
// e.g. ['index.html', 'sub-page/index.html', 'assets/images/favicon.png']
|
||||
const relativeFilePath = filePath.replace(`${directory}/`, '');
|
||||
const relativeFilePath = path.relative(directory, filePath);
|
||||
|
||||
// Convert destination file path to a POSIX path for uploading.
|
||||
// S3 expects / as path separator and relativeFilePath will contain \\ on Windows.
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html
|
||||
const relativeFilePathPosix = relativeFilePath
|
||||
.split(path.sep)
|
||||
.join(path.posix.sep);
|
||||
|
||||
const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;
|
||||
const destination = `${entityRootDir}/${relativeFilePath}`; // S3 Bucket file relative path
|
||||
const destination = `${entityRootDir}/${relativeFilePathPosix}`; // S3 Bucket file relative path
|
||||
|
||||
const fileContent = await fs.readFile(filePath, 'utf8');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user