Centralize lowercase logic to tested helper.

Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
Eric Peterson
2021-07-12 11:12:06 +02:00
parent 5ee76b0769
commit c23b09d52d
5 changed files with 62 additions and 37 deletions
@@ -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)