Centralize lowercase logic to tested helper.
Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
@@ -25,7 +25,11 @@ import createLimiter from 'p-limit';
|
||||
import path from 'path';
|
||||
import { Readable } from 'stream';
|
||||
import { Logger } from 'winston';
|
||||
import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers';
|
||||
import {
|
||||
getFileTreeRecursively,
|
||||
getHeadersForFileExtension,
|
||||
lowerCaseEntityTripletInStoragePath,
|
||||
} from './helpers';
|
||||
import {
|
||||
PublisherBase,
|
||||
PublishRequest,
|
||||
@@ -332,17 +336,10 @@ export class AwsS3Publish implements PublisherBase {
|
||||
await Promise.all(
|
||||
allObjects.map(f =>
|
||||
limiter(async file => {
|
||||
const [namespace, kind, name, ...parts] = file.split('/');
|
||||
const lowerNamespace = namespace.toLowerCase();
|
||||
const lowerKind = kind.toLowerCase();
|
||||
const lowerName = name.toLowerCase();
|
||||
const newPath = lowerCaseEntityTripletInStoragePath(file);
|
||||
|
||||
// If all parts are already lowercase, ignore.
|
||||
if (
|
||||
namespace === lowerNamespace &&
|
||||
kind === lowerKind &&
|
||||
name === lowerName
|
||||
) {
|
||||
if (file === newPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -352,7 +349,7 @@ export class AwsS3Publish implements PublisherBase {
|
||||
.copyObject({
|
||||
Bucket: this.bucketName,
|
||||
CopySource: [this.bucketName, file].join('/'),
|
||||
Key: [lowerNamespace, lowerKind, lowerName, ...parts].join('/'),
|
||||
Key: newPath,
|
||||
})
|
||||
.promise();
|
||||
|
||||
|
||||
@@ -25,7 +25,11 @@ import JSON5 from 'json5';
|
||||
import limiterFactory from 'p-limit';
|
||||
import { default as path, default as platformPath } from 'path';
|
||||
import { Logger } from 'winston';
|
||||
import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers';
|
||||
import {
|
||||
getFileTreeRecursively,
|
||||
getHeadersForFileExtension,
|
||||
lowerCaseEntityTripletInStoragePath,
|
||||
} from './helpers';
|
||||
import {
|
||||
PublisherBase,
|
||||
PublishRequest,
|
||||
@@ -290,14 +294,6 @@ export class AzureBlobStoragePublish implements PublisherBase {
|
||||
.exists();
|
||||
}
|
||||
|
||||
protected toLowerCase(originalName: string): string {
|
||||
const [namespace, kind, name, ...parts] = originalName.split('/');
|
||||
const lowerNamespace = namespace.toLowerCase();
|
||||
const lowerKind = kind.toLowerCase();
|
||||
const lowerName = name.toLowerCase();
|
||||
return [lowerNamespace, lowerKind, lowerName, ...parts].join('/');
|
||||
}
|
||||
|
||||
protected async renameBlob(
|
||||
originalName: string,
|
||||
newName: string,
|
||||
@@ -314,16 +310,16 @@ export class AzureBlobStoragePublish implements PublisherBase {
|
||||
}
|
||||
|
||||
protected async renameBlobToLowerCase(
|
||||
originalName: string,
|
||||
originalPath: string,
|
||||
removeOriginal: boolean,
|
||||
) {
|
||||
const newName = this.toLowerCase(originalName);
|
||||
if (originalName === newName) return;
|
||||
const newPath = lowerCaseEntityTripletInStoragePath(originalPath);
|
||||
if (originalPath === newPath) return;
|
||||
try {
|
||||
this.logger.debug(`Migrating ${originalName}`);
|
||||
await this.renameBlob(originalName, newName, removeOriginal);
|
||||
this.logger.debug(`Migrating ${originalPath}`);
|
||||
await this.renameBlob(originalPath, newPath, removeOriginal);
|
||||
} catch (e) {
|
||||
this.logger.warn(`Unable to migrate ${originalName}: ${e.message}`);
|
||||
this.logger.warn(`Unable to migrate ${originalPath}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
import mockFs from 'mock-fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers';
|
||||
import {
|
||||
getFileTreeRecursively,
|
||||
getHeadersForFileExtension,
|
||||
lowerCaseEntityTripletInStoragePath,
|
||||
} from './helpers';
|
||||
|
||||
describe('getHeadersForFileExtension', () => {
|
||||
const correctMapOfExtensions = [
|
||||
@@ -73,3 +77,17 @@ describe('getFileTreeRecursively', () => {
|
||||
expect(fileList).toContain(path.resolve(root, 'subDirA/file2'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('lowerCaseEntityTripletInStoragePath', () => {
|
||||
it('returns lower-cased entity triplet path', () => {
|
||||
const originalPath = 'default/Component/backstage/index.html';
|
||||
const actualPath = lowerCaseEntityTripletInStoragePath(originalPath);
|
||||
expect(actualPath).toBe('default/component/backstage/index.html');
|
||||
});
|
||||
|
||||
it('does not lowercase beyond the triplet', () => {
|
||||
const originalPath = 'default/Component/backstage/assets/IMAGE.png';
|
||||
const actualPath = lowerCaseEntityTripletInStoragePath(originalPath);
|
||||
expect(actualPath).toBe('default/component/backstage/assets/IMAGE.png');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -82,3 +82,24 @@ export const getFileTreeRecursively = async (
|
||||
});
|
||||
return fileList;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the version of an object's storage path where the first three parts
|
||||
* of the path (the entity triplet of namespace, kind, and name) are
|
||||
* lower-cased.
|
||||
*
|
||||
* Path must not include a starting slash.
|
||||
*
|
||||
* @example
|
||||
* lowerCaseEntityTripletInStoragePath('default/Component/backstage')
|
||||
* // return default/component/backstage
|
||||
*/
|
||||
export const lowerCaseEntityTripletInStoragePath = (
|
||||
originalPath: string,
|
||||
): string => {
|
||||
const [namespace, kind, name, ...parts] = originalPath.split('/');
|
||||
const lowerNamespace = namespace.toLowerCase();
|
||||
const lowerKind = kind.toLowerCase();
|
||||
const lowerName = name.toLowerCase();
|
||||
return [lowerNamespace, lowerKind, lowerName, ...parts].join('/');
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import { File } from '@google-cloud/storage';
|
||||
import { Writable } from 'stream';
|
||||
import { Logger } from 'winston';
|
||||
import { lowerCaseEntityTripletInStoragePath } from '../helpers';
|
||||
|
||||
/**
|
||||
* Writable stream to handle object copy/move operations. This implementation
|
||||
@@ -37,17 +38,10 @@ export class MigrateWriteStream extends Writable {
|
||||
|
||||
_write(file: File, _encoding: BufferEncoding, next: Function) {
|
||||
let shouldCallNext = true;
|
||||
const [namespace, kind, name, ...parts] = file.name.split('/');
|
||||
const lowerNamespace = namespace.toLowerCase();
|
||||
const lowerKind = kind.toLowerCase();
|
||||
const lowerName = name.toLowerCase();
|
||||
const newFile = lowerCaseEntityTripletInStoragePath(file.name);
|
||||
|
||||
// If all parts are already lowercase, ignore.
|
||||
if (
|
||||
namespace === lowerNamespace &&
|
||||
kind === lowerKind &&
|
||||
name === lowerName
|
||||
) {
|
||||
if (newFile === file.name) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
@@ -60,7 +54,6 @@ export class MigrateWriteStream extends Writable {
|
||||
}
|
||||
|
||||
// Otherwise, copy or move the file.
|
||||
const newFile = [lowerNamespace, lowerKind, lowerName, ...parts].join('/');
|
||||
// todo: Use file.move instead of file.copy when removeOriginal is true.
|
||||
const migrate = this.removeOriginal
|
||||
? file.copy.bind(file)
|
||||
|
||||
Reference in New Issue
Block a user