diff --git a/.gitignore b/.gitignore index 0cca676..089ba0a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -lib/ node_modules/ data registry.env diff --git a/lib/addTrailingSlash.d.ts b/lib/addTrailingSlash.d.ts new file mode 100644 index 0000000..a971090 --- /dev/null +++ b/lib/addTrailingSlash.d.ts @@ -0,0 +1,2 @@ +declare const _default: (path?: string | undefined) => string; +export default _default; diff --git a/lib/addTrailingSlash.js b/lib/addTrailingSlash.js new file mode 100644 index 0000000..f3c53ed --- /dev/null +++ b/lib/addTrailingSlash.js @@ -0,0 +1,13 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _default = path => { + return path != null ? path.endsWith('/') ? path : `${path}/` : ''; +}; + +exports.default = _default; +//# sourceMappingURL=addTrailingSlash.js.map \ No newline at end of file diff --git a/lib/addTrailingSlash.js.map b/lib/addTrailingSlash.js.map new file mode 100644 index 0000000..8550201 --- /dev/null +++ b/lib/addTrailingSlash.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/addTrailingSlash.ts"],"names":["path","endsWith"],"mappings":";;;;;;;eAAgBA,IAAD,IAA2B;AACxC,SAAOA,IAAI,IAAI,IAAR,GAAgBA,IAAI,CAACC,QAAL,CAAc,GAAd,IAAqBD,IAArB,GAA6B,GAAEA,IAAK,GAApD,GAA0D,EAAjE;AACD,C","sourcesContent":["export default (path?: string): string => {\n return path != null ? (path.endsWith('/') ? path : `${path}/`) : '';\n};\n"],"file":"addTrailingSlash.js"} \ No newline at end of file diff --git a/lib/config.d.ts b/lib/config.d.ts new file mode 100644 index 0000000..8822a69 --- /dev/null +++ b/lib/config.d.ts @@ -0,0 +1,13 @@ +import { Config } from '@verdaccio/types'; +export interface S3Config extends Config { + bucket: string; + keyPrefix: string; + endpoint?: string; + region?: string; + s3ForcePathStyle?: boolean; + acl?: string; + tarballACL?: string; + accessKeyId?: string; + secretAccessKey?: string; + sessionToken?: string; +} diff --git a/lib/config.js b/lib/config.js new file mode 100644 index 0000000..a8cfedc --- /dev/null +++ b/lib/config.js @@ -0,0 +1,6 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +//# sourceMappingURL=config.js.map \ No newline at end of file diff --git a/lib/config.js.map b/lib/config.js.map new file mode 100644 index 0000000..6ec1104 --- /dev/null +++ b/lib/config.js.map @@ -0,0 +1 @@ +{"version":3,"sources":[],"names":[],"mappings":"","sourcesContent":[],"file":"config.js"} \ No newline at end of file diff --git a/lib/deleteKeyPrefix.d.ts b/lib/deleteKeyPrefix.d.ts new file mode 100644 index 0000000..f589c64 --- /dev/null +++ b/lib/deleteKeyPrefix.d.ts @@ -0,0 +1,7 @@ +import { S3 } from 'aws-sdk'; +interface DeleteKeyPrefixOptions { + Bucket: string; + Prefix: string; +} +export declare function deleteKeyPrefix(s3: S3, options: DeleteKeyPrefixOptions, callback: (err: Error | null) => void): void; +export {}; diff --git a/lib/deleteKeyPrefix.js b/lib/deleteKeyPrefix.js new file mode 100644 index 0000000..db6a81d --- /dev/null +++ b/lib/deleteKeyPrefix.js @@ -0,0 +1,35 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.deleteKeyPrefix = deleteKeyPrefix; + +var _s3Errors = require("./s3Errors"); + +function deleteKeyPrefix(s3, options, callback) { + s3.listObjectsV2(options, (err, data) => { + if (err) { + callback((0, _s3Errors.convertS3Error)(err)); + } else if (data.KeyCount) { + const objectsToDelete = data.Contents ? data.Contents.map(s3Object => ({ + Key: s3Object.Key + })) : []; + s3.deleteObjects({ + Bucket: options.Bucket, + Delete: { + Objects: objectsToDelete + } + }, err => { + if (err) { + callback((0, _s3Errors.convertS3Error)(err)); + } else { + callback(null); + } + }); + } else { + callback((0, _s3Errors.create404Error)()); + } + }); +} +//# sourceMappingURL=deleteKeyPrefix.js.map \ No newline at end of file diff --git a/lib/deleteKeyPrefix.js.map b/lib/deleteKeyPrefix.js.map new file mode 100644 index 0000000..8ac6bde --- /dev/null +++ b/lib/deleteKeyPrefix.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/deleteKeyPrefix.ts"],"names":["deleteKeyPrefix","s3","options","callback","listObjectsV2","err","data","KeyCount","objectsToDelete","Contents","map","s3Object","Key","deleteObjects","Bucket","Delete","Objects"],"mappings":";;;;;;;AAEA;;AAOO,SAASA,eAAT,CAAyBC,EAAzB,EAAiCC,OAAjC,EAAkEC,QAAlE,EAA+G;AACpHF,EAAAA,EAAE,CAACG,aAAH,CAAiBF,OAAjB,EAA0B,CAACG,GAAD,EAAMC,IAAN,KAAe;AACvC,QAAID,GAAJ,EAAS;AACPF,MAAAA,QAAQ,CAAC,8BAAeE,GAAf,CAAD,CAAR;AACD,KAFD,MAEO,IAAIC,IAAI,CAACC,QAAT,EAAmB;AACxB,YAAMC,eAAwC,GAAGF,IAAI,CAACG,QAAL,GAC7CH,IAAI,CAACG,QAAL,CAAcC,GAAd,CAAkBC,QAAQ,KAAK;AAAEC,QAAAA,GAAG,EAAED,QAAQ,CAACC;AAAhB,OAAL,CAA1B,CAD6C,GAE7C,EAFJ;AAGAX,MAAAA,EAAE,CAACY,aAAH,CACE;AACEC,QAAAA,MAAM,EAAEZ,OAAO,CAACY,MADlB;AAEEC,QAAAA,MAAM,EAAE;AAAEC,UAAAA,OAAO,EAAER;AAAX;AAFV,OADF,EAKEH,GAAG,IAAI;AACL,YAAIA,GAAJ,EAAS;AACPF,UAAAA,QAAQ,CAAC,8BAAeE,GAAf,CAAD,CAAR;AACD,SAFD,MAEO;AACLF,UAAAA,QAAQ,CAAC,IAAD,CAAR;AACD;AACF,OAXH;AAaD,KAjBM,MAiBA;AACLA,MAAAA,QAAQ,CAAC,+BAAD,CAAR;AACD;AACF,GAvBD;AAwBD","sourcesContent":["import { S3 } from 'aws-sdk';\n\nimport { convertS3Error, create404Error } from './s3Errors';\n\ninterface DeleteKeyPrefixOptions {\n Bucket: string;\n Prefix: string;\n}\n\nexport function deleteKeyPrefix(s3: S3, options: DeleteKeyPrefixOptions, callback: (err: Error | null) => void): void {\n s3.listObjectsV2(options, (err, data) => {\n if (err) {\n callback(convertS3Error(err));\n } else if (data.KeyCount) {\n const objectsToDelete: S3.ObjectIdentifierList = data.Contents\n ? data.Contents.map(s3Object => ({ Key: s3Object.Key as S3.ObjectKey }))\n : [];\n s3.deleteObjects(\n {\n Bucket: options.Bucket,\n Delete: { Objects: objectsToDelete },\n },\n err => {\n if (err) {\n callback(convertS3Error(err));\n } else {\n callback(null);\n }\n }\n );\n } else {\n callback(create404Error());\n }\n });\n}\n"],"file":"deleteKeyPrefix.js"} \ No newline at end of file diff --git a/lib/index.d.ts b/lib/index.d.ts new file mode 100644 index 0000000..e7dd5a0 --- /dev/null +++ b/lib/index.d.ts @@ -0,0 +1,23 @@ +import { Logger, Config, Callback, IPluginStorage, PluginOptions, Token, TokenFilter } from '@verdaccio/types'; +import { S3Config } from './config'; +import S3PackageManager from './s3PackageManager'; +export default class S3Database implements IPluginStorage { + logger: Logger; + config: S3Config; + private s3; + private _localData; + constructor(config: Config, options: PluginOptions); + getSecret(): Promise; + setSecret(secret: string): Promise; + add(name: string, callback: Callback): void; + search(onPackage: Function, onEnd: Function): Promise; + private _fetchPackageInfo; + remove(name: string, callback: Callback): void; + get(callback: Callback): void; + private _sync; + getPackageStorage(packageName: string): S3PackageManager; + private _getData; + saveToken(token: Token): Promise; + deleteToken(user: string, tokenKey: string): Promise; + readTokens(filter: TokenFilter): Promise; +} diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..632be4a --- /dev/null +++ b/lib/index.js @@ -0,0 +1,313 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _commonsApi = require("@verdaccio/commons-api"); + +var _awsSdk = require("aws-sdk"); + +var _s3PackageManager = _interopRequireDefault(require("./s3PackageManager")); + +var _s3Errors = require("./s3Errors"); + +var _addTrailingSlash = _interopRequireDefault(require("./addTrailingSlash")); + +var _setConfigValue = _interopRequireDefault(require("./setConfigValue")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +class S3Database { + constructor(config, options) { + _defineProperty(this, "logger", void 0); + + _defineProperty(this, "config", void 0); + + _defineProperty(this, "s3", void 0); + + _defineProperty(this, "_localData", void 0); + + this.logger = options.logger; // copy so we don't mutate + + if (!config) { + throw new Error('s3 storage missing config. Add `store.s3-storage` to your config file'); + } + + this.config = Object.assign(config, config.store['aws-s3-storage']); + + if (!this.config.bucket) { + throw new Error('s3 storage requires a bucket'); + } + + this.config.bucket = (0, _setConfigValue.default)(this.config.bucket); + this.config.keyPrefix = (0, _setConfigValue.default)(this.config.keyPrefix); + this.config.endpoint = (0, _setConfigValue.default)(this.config.endpoint); + this.config.region = (0, _setConfigValue.default)(this.config.region); + this.config.accessKeyId = (0, _setConfigValue.default)(this.config.accessKeyId); + this.config.secretAccessKey = (0, _setConfigValue.default)(this.config.secretAccessKey); + this.config.sessionToken = (0, _setConfigValue.default)(this.config.sessionToken); + this.config.acl = (0, _setConfigValue.default)(this.config.acl); + const configKeyPrefix = this.config.keyPrefix; + this._localData = null; + this.config.keyPrefix = (0, _addTrailingSlash.default)(configKeyPrefix); + this.logger.debug({ + config: JSON.stringify(this.config, null, 4) + }, 's3: configuration: @{config}'); + this.s3 = new _awsSdk.S3({ + endpoint: this.config.endpoint, + region: this.config.region, + s3ForcePathStyle: this.config.s3ForcePathStyle, + accessKeyId: this.config.accessKeyId, + secretAccessKey: this.config.secretAccessKey, + sessionToken: this.config.sessionToken + }); + } + + async getSecret() { + return Promise.resolve((await this._getData()).secret); + } + + async setSecret(secret) { + (await this._getData()).secret = secret; + await this._sync(); + } + + add(name, callback) { + this.logger.debug({ + name + }, 's3: [add] private package @{name}'); + + this._getData().then(async data => { + if (data.list.indexOf(name) === -1) { + data.list.push(name); + this.logger.trace({ + name + }, 's3: [add] @{name} has been added'); + + try { + await this._sync(); + callback(null); + } catch (err) { + callback(err); + } + } else { + callback(null); + } + }); + } + + async search(onPackage, onEnd) { + this.logger.debug('s3: [search]'); + const storage = await this._getData(); + const storageInfoMap = storage.list.map(this._fetchPackageInfo.bind(this, onPackage)); + this.logger.debug({ + l: storageInfoMap.length + }, 's3: [search] storageInfoMap length is @{l}'); + await Promise.all(storageInfoMap); + onEnd(); + } + + async _fetchPackageInfo(onPackage, packageName) { + const { + bucket, + keyPrefix + } = this.config; + this.logger.debug({ + packageName + }, 's3: [_fetchPackageInfo] @{packageName}'); + this.logger.trace({ + keyPrefix, + bucket + }, 's3: [_fetchPackageInfo] bucket: @{bucket} prefix: @{keyPrefix}'); + return new Promise(resolve => { + this.s3.headObject({ + Bucket: bucket, + Key: `${keyPrefix + packageName}/package.json` + }, (err, response) => { + if (err) { + this.logger.debug({ + err + }, 's3: [_fetchPackageInfo] error: @{err}'); + return resolve(); + } + + if (response.LastModified) { + const { + LastModified + } = response; + this.logger.trace({ + LastModified + }, 's3: [_fetchPackageInfo] LastModified: @{LastModified}'); + return onPackage({ + name: packageName, + path: packageName, + time: LastModified.getTime() + }, resolve); + } + + resolve(); + }); + }); + } + + remove(name, callback) { + this.logger.debug({ + name + }, 's3: [remove] @{name}'); + this.get(async (err, data) => { + if (err) { + this.logger.error({ + err + }, 's3: [remove] error: @{err}'); + callback((0, _commonsApi.getInternalError)('something went wrong on remove a package')); + } + + const pkgName = data.indexOf(name); + + if (pkgName !== -1) { + const data = await this._getData(); + data.list.splice(pkgName, 1); + this.logger.debug({ + pkgName + }, 's3: [remove] sucessfully removed @{pkgName}'); + } + + try { + this.logger.trace('s3: [remove] starting sync'); + await this._sync(); + this.logger.trace('s3: [remove] finish sync'); + callback(null); + } catch (err) { + this.logger.error({ + err + }, 's3: [remove] sync error: @{err}'); + callback(err); + } + }); + } + + get(callback) { + this.logger.debug('s3: [get]'); + + this._getData().then(data => callback(null, data.list)); + } // Create/write database file to s3 + + + async _sync() { + await new Promise((resolve, reject) => { + const { + bucket, + keyPrefix + } = this.config; + this.logger.debug({ + keyPrefix, + bucket + }, 's3: [_sync] bucket: @{bucket} prefix: @{keyPrefix}'); + this.s3.putObject({ + Bucket: this.config.bucket, + Key: `${this.config.keyPrefix}verdaccio-s3-db.json`, + Body: JSON.stringify(this._localData), + ACL: this.config.acl + }, err => { + if (err) { + this.logger.error({ + err + }, 's3: [_sync] error: @{err}'); + reject(err); + return; + } + + this.logger.debug('s3: [_sync] sucess'); + resolve(); + }); + }); + } // returns an instance of a class managing the storage for a single package + + + getPackageStorage(packageName) { + this.logger.debug({ + packageName + }, 's3: [getPackageStorage] @{packageName}'); + return new _s3PackageManager.default(this.config, packageName, this.logger); + } + + async _getData() { + if (!this._localData) { + this._localData = await new Promise((resolve, reject) => { + const { + bucket, + keyPrefix + } = this.config; + this.logger.debug({ + keyPrefix, + bucket + }, 's3: [_getData] bucket: @{bucket} prefix: @{keyPrefix}'); + this.logger.trace('s3: [_getData] get database object'); + this.s3.getObject({ + Bucket: bucket, + Key: `${keyPrefix}verdaccio-s3-db.json` + }, (err, response) => { + if (err) { + const s3Err = (0, _s3Errors.convertS3Error)(err); + this.logger.error({ + err: s3Err.message + }, 's3: [_getData] err: @{err}'); + + if ((0, _s3Errors.is404Error)(s3Err)) { + this.logger.error('s3: [_getData] err 404 create new database'); + resolve({ + list: [], + secret: '' + }); + } else { + reject(err); + } + + return; + } + + const body = response.Body ? response.Body.toString() : ''; + const data = JSON.parse(body); + this.logger.trace({ + body + }, 's3: [_getData] get data @{body}'); + resolve(data); + }); + }); + } else { + this.logger.trace('s3: [_getData] already exist'); + } + + return this._localData; + } + + saveToken(token) { + this.logger.warn({ + token + }, 'save token has not been implemented yet @{token}'); + return Promise.reject((0, _commonsApi.getServiceUnavailable)('[saveToken] method not implemented')); + } + + deleteToken(user, tokenKey) { + this.logger.warn({ + tokenKey, + user + }, 'delete token has not been implemented yet @{user}'); + return Promise.reject((0, _commonsApi.getServiceUnavailable)('[deleteToken] method not implemented')); + } + + readTokens(filter) { + this.logger.warn({ + filter + }, 'read tokens has not been implemented yet @{filter}'); + return Promise.reject((0, _commonsApi.getServiceUnavailable)('[readTokens] method not implemented')); + } + +} + +exports.default = S3Database; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/lib/index.js.map b/lib/index.js.map new file mode 100644 index 0000000..b26c31b --- /dev/null +++ b/lib/index.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/index.ts"],"names":["S3Database","constructor","config","options","logger","Error","Object","assign","store","bucket","keyPrefix","endpoint","region","accessKeyId","secretAccessKey","sessionToken","acl","configKeyPrefix","_localData","debug","JSON","stringify","s3","S3","s3ForcePathStyle","getSecret","Promise","resolve","_getData","secret","setSecret","_sync","add","name","callback","then","data","list","indexOf","push","trace","err","search","onPackage","onEnd","storage","storageInfoMap","map","_fetchPackageInfo","bind","l","length","all","packageName","headObject","Bucket","Key","response","LastModified","path","time","getTime","remove","get","error","pkgName","splice","reject","putObject","Body","ACL","getPackageStorage","S3PackageManager","getObject","s3Err","message","body","toString","parse","saveToken","token","warn","deleteToken","user","tokenKey","readTokens","filter"],"mappings":"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","sourcesContent":["import {\n LocalStorage,\n Logger,\n Config,\n Callback,\n IPluginStorage,\n PluginOptions,\n Token,\n TokenFilter,\n} from '@verdaccio/types';\nimport { getInternalError, VerdaccioError, getServiceUnavailable } from '@verdaccio/commons-api';\nimport { S3 } from 'aws-sdk';\n\nimport { S3Config } from './config';\nimport S3PackageManager from './s3PackageManager';\nimport { convertS3Error, is404Error } from './s3Errors';\nimport addTrailingSlash from './addTrailingSlash';\nimport setConfigValue from './setConfigValue';\n\nexport default class S3Database implements IPluginStorage {\n public logger: Logger;\n public config: S3Config;\n private s3: S3;\n private _localData: LocalStorage | null;\n\n public constructor(config: Config, options: PluginOptions) {\n this.logger = options.logger;\n // copy so we don't mutate\n if (!config) {\n throw new Error('s3 storage missing config. Add `store.s3-storage` to your config file');\n }\n this.config = Object.assign(config, config.store['aws-s3-storage']);\n\n if (!this.config.bucket) {\n throw new Error('s3 storage requires a bucket');\n }\n\n this.config.bucket = setConfigValue(this.config.bucket);\n this.config.keyPrefix = setConfigValue(this.config.keyPrefix);\n this.config.endpoint = setConfigValue(this.config.endpoint);\n this.config.region = setConfigValue(this.config.region);\n this.config.accessKeyId = setConfigValue(this.config.accessKeyId);\n this.config.secretAccessKey = setConfigValue(this.config.secretAccessKey);\n this.config.sessionToken = setConfigValue(this.config.sessionToken);\n this.config.acl = setConfigValue(this.config.acl);\n\n const configKeyPrefix = this.config.keyPrefix;\n this._localData = null;\n this.config.keyPrefix = addTrailingSlash(configKeyPrefix);\n\n this.logger.debug({ config: JSON.stringify(this.config, null, 4) }, 's3: configuration: @{config}');\n\n this.s3 = new S3({\n endpoint: this.config.endpoint,\n region: this.config.region,\n s3ForcePathStyle: this.config.s3ForcePathStyle,\n accessKeyId: this.config.accessKeyId,\n secretAccessKey: this.config.secretAccessKey,\n sessionToken: this.config.sessionToken,\n });\n }\n\n public async getSecret(): Promise {\n return Promise.resolve((await this._getData()).secret);\n }\n\n public async setSecret(secret: string): Promise {\n (await this._getData()).secret = secret;\n await this._sync();\n }\n\n public add(name: string, callback: Callback): void {\n this.logger.debug({ name }, 's3: [add] private package @{name}');\n this._getData().then(async data => {\n if (data.list.indexOf(name) === -1) {\n data.list.push(name);\n this.logger.trace({ name }, 's3: [add] @{name} has been added');\n try {\n await this._sync();\n callback(null);\n } catch (err) {\n callback(err);\n }\n } else {\n callback(null);\n }\n });\n }\n\n public async search(onPackage: Function, onEnd: Function): Promise {\n this.logger.debug('s3: [search]');\n const storage = await this._getData();\n const storageInfoMap = storage.list.map(this._fetchPackageInfo.bind(this, onPackage));\n this.logger.debug({ l: storageInfoMap.length }, 's3: [search] storageInfoMap length is @{l}');\n await Promise.all(storageInfoMap);\n onEnd();\n }\n\n private async _fetchPackageInfo(onPackage: Function, packageName: string): Promise {\n const { bucket, keyPrefix } = this.config;\n this.logger.debug({ packageName }, 's3: [_fetchPackageInfo] @{packageName}');\n this.logger.trace({ keyPrefix, bucket }, 's3: [_fetchPackageInfo] bucket: @{bucket} prefix: @{keyPrefix}');\n return new Promise((resolve): void => {\n this.s3.headObject(\n {\n Bucket: bucket,\n Key: `${keyPrefix + packageName}/package.json`,\n },\n (err, response) => {\n if (err) {\n this.logger.debug({ err }, 's3: [_fetchPackageInfo] error: @{err}');\n return resolve();\n }\n if (response.LastModified) {\n const { LastModified } = response;\n this.logger.trace({ LastModified }, 's3: [_fetchPackageInfo] LastModified: @{LastModified}');\n return onPackage(\n {\n name: packageName,\n path: packageName,\n time: LastModified.getTime(),\n },\n resolve\n );\n }\n resolve();\n }\n );\n });\n }\n\n public remove(name: string, callback: Callback): void {\n this.logger.debug({ name }, 's3: [remove] @{name}');\n this.get(async (err, data) => {\n if (err) {\n this.logger.error({ err }, 's3: [remove] error: @{err}');\n callback(getInternalError('something went wrong on remove a package'));\n }\n\n const pkgName = data.indexOf(name);\n if (pkgName !== -1) {\n const data = await this._getData();\n data.list.splice(pkgName, 1);\n this.logger.debug({ pkgName }, 's3: [remove] sucessfully removed @{pkgName}');\n }\n\n try {\n this.logger.trace('s3: [remove] starting sync');\n await this._sync();\n this.logger.trace('s3: [remove] finish sync');\n callback(null);\n } catch (err) {\n this.logger.error({ err }, 's3: [remove] sync error: @{err}');\n callback(err);\n }\n });\n }\n\n public get(callback: Callback): void {\n this.logger.debug('s3: [get]');\n this._getData().then(data => callback(null, data.list));\n }\n\n // Create/write database file to s3\n private async _sync(): Promise {\n await new Promise((resolve, reject): void => {\n const { bucket, keyPrefix } = this.config;\n this.logger.debug({ keyPrefix, bucket }, 's3: [_sync] bucket: @{bucket} prefix: @{keyPrefix}');\n this.s3.putObject(\n {\n Bucket: this.config.bucket,\n Key: `${this.config.keyPrefix}verdaccio-s3-db.json`,\n Body: JSON.stringify(this._localData),\n ACL: this.config.acl,\n },\n err => {\n if (err) {\n this.logger.error({ err }, 's3: [_sync] error: @{err}');\n reject(err);\n return;\n }\n this.logger.debug('s3: [_sync] sucess');\n resolve();\n }\n );\n });\n }\n\n // returns an instance of a class managing the storage for a single package\n public getPackageStorage(packageName: string): S3PackageManager {\n this.logger.debug({ packageName }, 's3: [getPackageStorage] @{packageName}');\n\n return new S3PackageManager(this.config, packageName, this.logger);\n }\n\n private async _getData(): Promise {\n if (!this._localData) {\n this._localData = await new Promise((resolve, reject): void => {\n const { bucket, keyPrefix } = this.config;\n this.logger.debug({ keyPrefix, bucket }, 's3: [_getData] bucket: @{bucket} prefix: @{keyPrefix}');\n this.logger.trace('s3: [_getData] get database object');\n this.s3.getObject(\n {\n Bucket: bucket,\n Key: `${keyPrefix}verdaccio-s3-db.json`,\n },\n (err, response) => {\n if (err) {\n const s3Err: VerdaccioError = convertS3Error(err);\n this.logger.error({ err: s3Err.message }, 's3: [_getData] err: @{err}');\n if (is404Error(s3Err)) {\n this.logger.error('s3: [_getData] err 404 create new database');\n resolve({ list: [], secret: '' });\n } else {\n reject(err);\n }\n return;\n }\n\n const body = response.Body ? response.Body.toString() : '';\n const data = JSON.parse(body);\n this.logger.trace({ body }, 's3: [_getData] get data @{body}');\n resolve(data);\n }\n );\n });\n } else {\n this.logger.trace('s3: [_getData] already exist');\n }\n\n return this._localData as LocalStorage;\n }\n\n public saveToken(token: Token): Promise {\n this.logger.warn({ token }, 'save token has not been implemented yet @{token}');\n\n return Promise.reject(getServiceUnavailable('[saveToken] method not implemented'));\n }\n\n public deleteToken(user: string, tokenKey: string): Promise {\n this.logger.warn({ tokenKey, user }, 'delete token has not been implemented yet @{user}');\n\n return Promise.reject(getServiceUnavailable('[deleteToken] method not implemented'));\n }\n\n public readTokens(filter: TokenFilter): Promise {\n this.logger.warn({ filter }, 'read tokens has not been implemented yet @{filter}');\n\n return Promise.reject(getServiceUnavailable('[readTokens] method not implemented'));\n }\n}\n"],"file":"index.js"} \ No newline at end of file diff --git a/lib/s3Errors.d.ts b/lib/s3Errors.d.ts new file mode 100644 index 0000000..b0c5309 --- /dev/null +++ b/lib/s3Errors.d.ts @@ -0,0 +1,9 @@ +import { AWSError } from 'aws-sdk'; +import { VerdaccioError } from '@verdaccio/commons-api'; +export declare function is404Error(err: VerdaccioError): boolean; +export declare function create404Error(): VerdaccioError; +export declare function is409Error(err: VerdaccioError): boolean; +export declare function create409Error(): VerdaccioError; +export declare function is503Error(err: VerdaccioError): boolean; +export declare function create503Error(): VerdaccioError; +export declare function convertS3Error(err: AWSError): VerdaccioError; diff --git a/lib/s3Errors.js b/lib/s3Errors.js new file mode 100644 index 0000000..987575c --- /dev/null +++ b/lib/s3Errors.js @@ -0,0 +1,57 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.convertS3Error = convertS3Error; +exports.create404Error = create404Error; +exports.create409Error = create409Error; +exports.create503Error = create503Error; +exports.is404Error = is404Error; +exports.is409Error = is409Error; +exports.is503Error = is503Error; + +var _commonsApi = require("@verdaccio/commons-api"); + +function is404Error(err) { + return err.code === _commonsApi.HTTP_STATUS.NOT_FOUND; +} + +function create404Error() { + return (0, _commonsApi.getNotFound)('no such package available'); +} + +function is409Error(err) { + return err.code === _commonsApi.HTTP_STATUS.CONFLICT; +} + +function create409Error() { + return (0, _commonsApi.getConflict)('file already exists'); +} + +function is503Error(err) { + return err.code === _commonsApi.HTTP_STATUS.SERVICE_UNAVAILABLE; +} + +function create503Error() { + return (0, _commonsApi.getCode)(_commonsApi.HTTP_STATUS.SERVICE_UNAVAILABLE, 'resource temporarily unavailable'); +} + +function convertS3Error(err) { + switch (err.code) { + case 'NoSuchKey': + case 'NotFound': + return (0, _commonsApi.getNotFound)(); + + case 'StreamContentLengthMismatch': + return (0, _commonsApi.getInternalError)(_commonsApi.API_ERROR.CONTENT_MISMATCH); + + case 'RequestAbortedError': + return (0, _commonsApi.getInternalError)('request aborted'); + + default: + // @ts-ignore + return (0, _commonsApi.getCode)(err.statusCode, err.message); + } +} +//# sourceMappingURL=s3Errors.js.map \ No newline at end of file diff --git a/lib/s3Errors.js.map b/lib/s3Errors.js.map new file mode 100644 index 0000000..7f87ea7 --- /dev/null +++ b/lib/s3Errors.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/s3Errors.ts"],"names":["is404Error","err","code","HTTP_STATUS","NOT_FOUND","create404Error","is409Error","CONFLICT","create409Error","is503Error","SERVICE_UNAVAILABLE","create503Error","convertS3Error","API_ERROR","CONTENT_MISMATCH","statusCode","message"],"mappings":";;;;;;;;;;;;;AACA;;AAUO,SAASA,UAAT,CAAoBC,GAApB,EAAkD;AACvD,SAAOA,GAAG,CAACC,IAAJ,KAAaC,wBAAYC,SAAhC;AACD;;AAEM,SAASC,cAAT,GAA0C;AAC/C,SAAO,6BAAY,2BAAZ,CAAP;AACD;;AAEM,SAASC,UAAT,CAAoBL,GAApB,EAAkD;AACvD,SAAOA,GAAG,CAACC,IAAJ,KAAaC,wBAAYI,QAAhC;AACD;;AAEM,SAASC,cAAT,GAA0C;AAC/C,SAAO,6BAAY,qBAAZ,CAAP;AACD;;AAEM,SAASC,UAAT,CAAoBR,GAApB,EAAkD;AACvD,SAAOA,GAAG,CAACC,IAAJ,KAAaC,wBAAYO,mBAAhC;AACD;;AAEM,SAASC,cAAT,GAA0C;AAC/C,SAAO,yBAAQR,wBAAYO,mBAApB,EAAyC,kCAAzC,CAAP;AACD;;AAEM,SAASE,cAAT,CAAwBX,GAAxB,EAAuD;AAC5D,UAAQA,GAAG,CAACC,IAAZ;AACE,SAAK,WAAL;AACA,SAAK,UAAL;AACE,aAAO,8BAAP;;AACF,SAAK,6BAAL;AACE,aAAO,kCAAiBW,sBAAUC,gBAA3B,CAAP;;AACF,SAAK,qBAAL;AACE,aAAO,kCAAiB,iBAAjB,CAAP;;AACF;AACE;AACA,aAAO,yBAAQb,GAAG,CAACc,UAAZ,EAAwBd,GAAG,CAACe,OAA5B,CAAP;AAVJ;AAYD","sourcesContent":["import { AWSError } from 'aws-sdk';\nimport {\n getNotFound,\n getCode,\n getInternalError,\n getConflict,\n API_ERROR,\n HTTP_STATUS,\n VerdaccioError,\n} from '@verdaccio/commons-api';\n\nexport function is404Error(err: VerdaccioError): boolean {\n return err.code === HTTP_STATUS.NOT_FOUND;\n}\n\nexport function create404Error(): VerdaccioError {\n return getNotFound('no such package available');\n}\n\nexport function is409Error(err: VerdaccioError): boolean {\n return err.code === HTTP_STATUS.CONFLICT;\n}\n\nexport function create409Error(): VerdaccioError {\n return getConflict('file already exists');\n}\n\nexport function is503Error(err: VerdaccioError): boolean {\n return err.code === HTTP_STATUS.SERVICE_UNAVAILABLE;\n}\n\nexport function create503Error(): VerdaccioError {\n return getCode(HTTP_STATUS.SERVICE_UNAVAILABLE, 'resource temporarily unavailable');\n}\n\nexport function convertS3Error(err: AWSError): VerdaccioError {\n switch (err.code) {\n case 'NoSuchKey':\n case 'NotFound':\n return getNotFound();\n case 'StreamContentLengthMismatch':\n return getInternalError(API_ERROR.CONTENT_MISMATCH);\n case 'RequestAbortedError':\n return getInternalError('request aborted');\n default:\n // @ts-ignore\n return getCode(err.statusCode, err.message);\n }\n}\n"],"file":"s3Errors.js"} \ No newline at end of file diff --git a/lib/s3PackageManager.d.ts b/lib/s3PackageManager.d.ts new file mode 100644 index 0000000..5799059 --- /dev/null +++ b/lib/s3PackageManager.d.ts @@ -0,0 +1,22 @@ +import { UploadTarball, ReadTarball } from '@verdaccio/streams'; +import { Callback, Logger, Package, ILocalPackageManager, CallbackAction, ReadPackageCallback } from '@verdaccio/types'; +import { S3Config } from './config'; +export default class S3PackageManager implements ILocalPackageManager { + config: S3Config; + logger: Logger; + private readonly packageName; + private readonly s3; + private readonly packagePath; + private readonly tarballACL; + private readonly acl; + constructor(config: S3Config, packageName: string, logger: Logger); + updatePackage(name: string, updateHandler: Callback, onWrite: Callback, transformPackage: Function, onEnd: Callback): void; + private _getData; + deletePackage(fileName: string, callback: Callback): void; + removePackage(callback: CallbackAction): void; + createPackage(name: string, value: Package, callback: CallbackAction): void; + savePackage(name: string, value: Package, callback: CallbackAction): void; + readPackage(name: string, callback: ReadPackageCallback): void; + writeTarball(name: string): UploadTarball; + readTarball(name: string): ReadTarball; +} diff --git a/lib/s3PackageManager.js b/lib/s3PackageManager.js new file mode 100644 index 0000000..42ba1d0 --- /dev/null +++ b/lib/s3PackageManager.js @@ -0,0 +1,470 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _awsSdk = require("aws-sdk"); + +var _streams = require("@verdaccio/streams"); + +var _commonsApi = require("@verdaccio/commons-api"); + +var _s3Errors = require("./s3Errors"); + +var _deleteKeyPrefix = require("./deleteKeyPrefix"); + +var _addTrailingSlash = _interopRequireDefault(require("./addTrailingSlash")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +const pkgFileName = 'package.json'; + +class S3PackageManager { + constructor(config, packageName, logger) { + _defineProperty(this, "config", void 0); + + _defineProperty(this, "logger", void 0); + + _defineProperty(this, "packageName", void 0); + + _defineProperty(this, "s3", void 0); + + _defineProperty(this, "packagePath", void 0); + + _defineProperty(this, "tarballACL", void 0); + + _defineProperty(this, "acl", void 0); + + this.config = config; + this.packageName = packageName; + this.logger = logger; + const { + endpoint, + region, + s3ForcePathStyle, + accessKeyId, + secretAccessKey, + sessionToken, + tarballACL, + acl + } = config; + this.tarballACL = tarballACL || 'private'; + this.acl = acl || 'private'; + this.s3 = new _awsSdk.S3({ + endpoint, + region, + s3ForcePathStyle, + accessKeyId, + secretAccessKey, + sessionToken + }); + this.logger.trace({ + packageName + }, 's3: [S3PackageManager constructor] packageName @{packageName}'); + this.logger.trace({ + endpoint + }, 's3: [S3PackageManager constructor] endpoint @{endpoint}'); + this.logger.trace({ + region + }, 's3: [S3PackageManager constructor] region @{region}'); + this.logger.trace({ + s3ForcePathStyle + }, 's3: [S3PackageManager constructor] s3ForcePathStyle @{s3ForcePathStyle}'); + this.logger.trace({ + acl + }, 's3: [S3PackageManager constructor] acl @{acl}'); + this.logger.trace({ + tarballACL + }, 's3: [S3PackageManager constructor] tarballACL @{tarballACL}'); + this.logger.trace({ + accessKeyId + }, 's3: [S3PackageManager constructor] accessKeyId @{accessKeyId}'); + this.logger.trace({ + secretAccessKey + }, 's3: [S3PackageManager constructor] secretAccessKey @{secretAccessKey}'); + this.logger.trace({ + sessionToken + }, 's3: [S3PackageManager constructor] sessionToken @{sessionToken}'); + const packageAccess = this.config.getMatchedPackagesSpec(packageName); + + if (packageAccess) { + const storage = packageAccess.storage; + const packageCustomFolder = (0, _addTrailingSlash.default)(storage); + this.packagePath = `${this.config.keyPrefix}${packageCustomFolder}${this.packageName}`; + } else { + this.packagePath = `${this.config.keyPrefix}${this.packageName}`; + } + } + + updatePackage(name, updateHandler, onWrite, transformPackage, onEnd) { + this.logger.debug({ + name + }, 's3: [S3PackageManager updatePackage init] @{name}'); + + (async () => { + try { + const json = await this._getData(); + updateHandler(json, err => { + if (err) { + this.logger.error({ + err + }, 's3: [S3PackageManager updatePackage updateHandler onEnd] @{err}'); + onEnd(err); + } else { + const transformedPackage = transformPackage(json); + this.logger.debug({ + transformedPackage + }, 's3: [S3PackageManager updatePackage updateHandler onWrite] @{transformedPackage}'); + onWrite(name, transformedPackage, onEnd); + } + }); + } catch (err) { + this.logger.error({ + err + }, 's3: [S3PackageManager updatePackage updateHandler onEnd catch] @{err}'); + return onEnd(err); + } + })(); + } + + async _getData() { + this.logger.debug('s3: [S3PackageManager _getData init]'); + return await new Promise((resolve, reject) => { + this.s3.getObject({ + Bucket: this.config.bucket, + Key: `${this.packagePath}/${pkgFileName}` + }, (err, response) => { + if (err) { + this.logger.error({ + err: err.message + }, 's3: [S3PackageManager _getData] aws @{err}'); + const error = (0, _s3Errors.convertS3Error)(err); + this.logger.error({ + error: err.message + }, 's3: [S3PackageManager _getData] @{error}'); + reject(error); + return; + } + + const body = response.Body ? response.Body.toString() : ''; + let data; + + try { + data = JSON.parse(body); + } catch (e) { + this.logger.error({ + body + }, 's3: [S3PackageManager _getData] error parsing: @{body}'); + reject(e); + return; + } + + this.logger.trace({ + data + }, 's3: [S3PackageManager _getData body] @{data.name}'); + resolve(data); + }); + }); + } + + deletePackage(fileName, callback) { + this.s3.deleteObject({ + Bucket: this.config.bucket, + Key: `${this.packagePath}/${fileName}` + }, err => { + if (err) { + callback(err); + } else { + callback(null); + } + }); + } + + removePackage(callback) { + (0, _deleteKeyPrefix.deleteKeyPrefix)(this.s3, { + Bucket: this.config.bucket, + Prefix: (0, _addTrailingSlash.default)(this.packagePath) + }, function (err) { + if (err && (0, _s3Errors.is404Error)(err)) { + callback(null); + } else { + callback(err); + } + }); + } + + createPackage(name, value, callback) { + this.logger.debug({ + name, + packageName: this.packageName + }, 's3: [S3PackageManager createPackage init] name @{name}/@{packageName}'); + this.logger.trace({ + value + }, 's3: [S3PackageManager createPackage init] name @value'); + this.s3.headObject({ + Bucket: this.config.bucket, + Key: `${this.packagePath}/${pkgFileName}` + }, (err, data) => { + if (err) { + const s3Err = (0, _s3Errors.convertS3Error)(err); // only allow saving if this file doesn't exist already + + if ((0, _s3Errors.is404Error)(s3Err)) { + this.logger.debug({ + s3Err + }, 's3: [S3PackageManager createPackage] 404 package not found]'); + this.savePackage(name, value, callback); + this.logger.trace({ + data + }, 's3: [S3PackageManager createPackage] package saved data from s3: @{data}'); + } else { + this.logger.error({ + s3Err: s3Err.message + }, 's3: [S3PackageManager createPackage error] @s3Err'); + callback(s3Err); + } + } else { + this.logger.debug('s3: [S3PackageManager createPackage ] package exist already'); + callback((0, _s3Errors.create409Error)()); + } + }); + } + + savePackage(name, value, callback) { + this.logger.debug({ + name, + packageName: this.packageName + }, 's3: [S3PackageManager savePackage init] name @{name}/@{packageName}'); + this.logger.trace({ + value + }, 's3: [S3PackageManager savePackage ] init value @{value}'); + this.s3.putObject({ + // TODO: not sure whether save the object with spaces will increase storage size + Body: JSON.stringify(value, null, ' '), + Bucket: this.config.bucket, + Key: `${this.packagePath}/${pkgFileName}`, + ACL: this.config.acl + }, callback); + } + + readPackage(name, callback) { + this.logger.debug({ + name, + packageName: this.packageName + }, 's3: [S3PackageManager readPackage init] name @{name}/@{packageName}'); + + (async () => { + try { + const data = await this._getData(); + this.logger.trace({ + data, + packageName: this.packageName + }, 's3: [S3PackageManager readPackage] packageName: @{packageName} / data @{data}'); + callback(null, data); + } catch (err) { + this.logger.error({ + err: err.message + }, 's3: [S3PackageManager readPackage] @{err}'); + callback(err); + } + })(); + } + + writeTarball(name) { + this.logger.debug({ + name, + packageName: this.packageName + }, 's3: [S3PackageManager writeTarball init] name @{name}/@{packageName}'); + const uploadStream = new _streams.UploadTarball({}); + let streamEnded = 0; + uploadStream.on('end', () => { + this.logger.debug({ + name, + packageName: this.packageName + }, 's3: [S3PackageManager writeTarball event: end] name @{name}/@{packageName}'); + streamEnded = 1; + }); + const baseS3Params = { + Bucket: this.config.bucket, + Key: `${this.packagePath}/${name}` + }; // NOTE: I'm using listObjectVersions so I don't have to download the full object with getObject. + // Preferably, I'd use getObjectMetadata or getDetails when it's available in the node sdk + // TODO: convert to headObject + + this.s3.headObject({ + Bucket: this.config.bucket, + Key: `${this.packagePath}/${name}` + }, err => { + if (err) { + const convertedErr = (0, _s3Errors.convertS3Error)(err); + this.logger.error({ + error: convertedErr.message + }, 's3: [S3PackageManager writeTarball headObject] @{error}'); + + if ((0, _s3Errors.is404Error)(convertedErr) === false) { + this.logger.error({ + error: convertedErr.message + }, 's3: [S3PackageManager writeTarball headObject] non a 404 emit error: @{error}'); + uploadStream.emit('error', convertedErr); + } else { + this.logger.debug('s3: [S3PackageManager writeTarball managedUpload] init stream'); + const managedUpload = this.s3.upload(Object.assign({}, baseS3Params, { + Body: uploadStream, + ACL: this.tarballACL + })); // NOTE: there's a managedUpload.promise, but it doesn't seem to work + + const promise = new Promise(resolve => { + this.logger.debug('s3: [S3PackageManager writeTarball managedUpload] send'); + managedUpload.send((err, data) => { + if (err) { + const error = (0, _s3Errors.convertS3Error)(err); + this.logger.error({ + error: error.message + }, 's3: [S3PackageManager writeTarball managedUpload send] emit error @{error}'); + uploadStream.emit('error', error); + } else { + this.logger.trace({ + data + }, 's3: [S3PackageManager writeTarball managedUpload send] response @{data}'); + resolve(); + } + }); + this.logger.debug({ + name + }, 's3: [S3PackageManager writeTarball uploadStream] emit open @{name}'); + uploadStream.emit('open'); + }); + + uploadStream.done = () => { + const onEnd = async () => { + try { + await promise; + this.logger.debug('s3: [S3PackageManager writeTarball uploadStream done] emit success'); + uploadStream.emit('success'); + } catch (err) { + // already emitted in the promise above, necessary because of some issues + // with promises in jest + this.logger.error({ + err + }, 's3: [S3PackageManager writeTarball uploadStream done] error @{err}'); + } + }; + + if (streamEnded) { + this.logger.trace({ + name + }, 's3: [S3PackageManager writeTarball uploadStream] streamEnded true @{name}'); + onEnd(); + } else { + this.logger.trace({ + name + }, 's3: [S3PackageManager writeTarball uploadStream] streamEnded false emit end @{name}'); + uploadStream.on('end', onEnd); + } + }; + + uploadStream.abort = () => { + this.logger.debug('s3: [S3PackageManager writeTarball uploadStream abort] init'); + + try { + this.logger.debug('s3: [S3PackageManager writeTarball managedUpload abort]'); + managedUpload.abort(); + } catch (err) { + const error = (0, _s3Errors.convertS3Error)(err); + uploadStream.emit('error', error); + this.logger.error({ + error + }, 's3: [S3PackageManager writeTarball uploadStream error] emit error @{error}'); + } finally { + this.logger.debug({ + name, + baseS3Params + }, 's3: [S3PackageManager writeTarball uploadStream abort] s3.deleteObject @{name}/@{baseS3Params}'); + this.s3.deleteObject(baseS3Params); + } + }; + } + } else { + this.logger.debug({ + name + }, 's3: [S3PackageManager writeTarball headObject] emit error @{name} 409'); + uploadStream.emit('error', (0, _s3Errors.create409Error)()); + } + }); + return uploadStream; + } + + readTarball(name) { + this.logger.debug({ + name, + packageName: this.packageName + }, 's3: [S3PackageManager readTarball init] name @{name}/@{packageName}'); + const readTarballStream = new _streams.ReadTarball({}); + const request = this.s3.getObject({ + Bucket: this.config.bucket, + Key: `${this.packagePath}/${name}` + }); + let headersSent = false; + const readStream = request.on('httpHeaders', (statusCode, headers) => { + // don't process status code errors here, we'll do that in readStream.on('error' + // otherwise they'll be processed twice + // verdaccio force garbage collects a stream on 404, so we can't emit more + // than one error or it'll fail + // https://github.com/verdaccio/verdaccio/blob/c1bc261/src/lib/storage.js#L178 + this.logger.debug({ + name, + packageName: this.packageName + }, 's3: [S3PackageManager readTarball httpHeaders] name @{name}/@{packageName}'); + this.logger.trace({ + headers + }, 's3: [S3PackageManager readTarball httpHeaders event] headers @headers'); + this.logger.trace({ + statusCode + }, 's3: [S3PackageManager readTarball httpHeaders event] statusCode @{statusCode}'); + + if (statusCode !== _commonsApi.HTTP_STATUS.NOT_FOUND) { + if (headers[_commonsApi.HEADERS.CONTENT_LENGTH]) { + const contentLength = parseInt(headers[_commonsApi.HEADERS.CONTENT_LENGTH], 10); // not sure this is necessary + + if (headersSent) { + return; + } + + headersSent = true; + this.logger.debug('s3: [S3PackageManager readTarball readTarballStream event] emit content-length'); + readTarballStream.emit(_commonsApi.HEADERS.CONTENT_LENGTH, contentLength); // we know there's content, so open the stream + + readTarballStream.emit('open'); + this.logger.debug('s3: [S3PackageManager readTarball readTarballStream event] emit open'); + } + } else { + this.logger.trace('s3: [S3PackageManager readTarball httpHeaders event] not found, avoid emit open file'); + } + }).createReadStream(); + readStream.on('error', err => { + const error = (0, _s3Errors.convertS3Error)(err); + readTarballStream.emit('error', error); + this.logger.error({ + error: error.message + }, 's3: [S3PackageManager readTarball readTarballStream event] error @{error}'); + }); + this.logger.trace('s3: [S3PackageManager readTarball readTarballStream event] pipe'); + readStream.pipe(readTarballStream); + + readTarballStream.abort = () => { + this.logger.debug('s3: [S3PackageManager readTarball readTarballStream event] request abort'); + request.abort(); + this.logger.debug('s3: [S3PackageManager readTarball readTarballStream event] request destroy'); + readStream.destroy(); + }; + + return readTarballStream; + } + +} + +exports.default = S3PackageManager; +//# sourceMappingURL=s3PackageManager.js.map \ No newline at end of file diff --git a/lib/s3PackageManager.js.map b/lib/s3PackageManager.js.map new file mode 100644 index 0000000..68c24a1 --- /dev/null +++ b/lib/s3PackageManager.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/s3PackageManager.ts"],"names":["pkgFileName","S3PackageManager","constructor","config","packageName","logger","endpoint","region","s3ForcePathStyle","accessKeyId","secretAccessKey","sessionToken","tarballACL","acl","s3","S3","trace","packageAccess","getMatchedPackagesSpec","storage","packageCustomFolder","packagePath","keyPrefix","updatePackage","name","updateHandler","onWrite","transformPackage","onEnd","debug","json","_getData","err","error","transformedPackage","Promise","resolve","reject","getObject","Bucket","bucket","Key","response","message","body","Body","toString","data","JSON","parse","e","deletePackage","fileName","callback","deleteObject","removePackage","Prefix","createPackage","value","headObject","s3Err","savePackage","putObject","stringify","ACL","readPackage","writeTarball","uploadStream","UploadTarball","streamEnded","on","baseS3Params","convertedErr","emit","managedUpload","upload","Object","assign","promise","send","done","abort","readTarball","readTarballStream","ReadTarball","request","headersSent","readStream","statusCode","headers","HTTP_STATUS","NOT_FOUND","HEADERS","CONTENT_LENGTH","contentLength","parseInt","createReadStream","pipe","destroy"],"mappings":"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","sourcesContent":["import { S3, AWSError } from 'aws-sdk';\nimport { UploadTarball, ReadTarball } from '@verdaccio/streams';\nimport { HEADERS, HTTP_STATUS, VerdaccioError } from '@verdaccio/commons-api';\nimport { Callback, Logger, Package, ILocalPackageManager, CallbackAction, ReadPackageCallback } from '@verdaccio/types';\nimport { HttpError } from 'http-errors';\n\nimport { is404Error, convertS3Error, create409Error } from './s3Errors';\nimport { deleteKeyPrefix } from './deleteKeyPrefix';\nimport { S3Config } from './config';\nimport addTrailingSlash from './addTrailingSlash';\n\nconst pkgFileName = 'package.json';\n\nexport default class S3PackageManager implements ILocalPackageManager {\n public config: S3Config;\n public logger: Logger;\n private readonly packageName: string;\n private readonly s3: S3;\n private readonly packagePath: string;\n private readonly tarballACL: string;\n private readonly acl: string;\n\n public constructor(config: S3Config, packageName: string, logger: Logger) {\n this.config = config;\n this.packageName = packageName;\n this.logger = logger;\n const { endpoint, region, s3ForcePathStyle, accessKeyId, secretAccessKey, sessionToken, tarballACL, acl } = config;\n this.tarballACL = tarballACL || 'private';\n this.acl = acl || 'private';\n\n this.s3 = new S3({ endpoint, region, s3ForcePathStyle, accessKeyId, secretAccessKey, sessionToken });\n this.logger.trace({ packageName }, 's3: [S3PackageManager constructor] packageName @{packageName}');\n this.logger.trace({ endpoint }, 's3: [S3PackageManager constructor] endpoint @{endpoint}');\n this.logger.trace({ region }, 's3: [S3PackageManager constructor] region @{region}');\n this.logger.trace({ s3ForcePathStyle }, 's3: [S3PackageManager constructor] s3ForcePathStyle @{s3ForcePathStyle}');\n this.logger.trace({ acl }, 's3: [S3PackageManager constructor] acl @{acl}');\n this.logger.trace({ tarballACL }, 's3: [S3PackageManager constructor] tarballACL @{tarballACL}');\n this.logger.trace({ accessKeyId }, 's3: [S3PackageManager constructor] accessKeyId @{accessKeyId}');\n this.logger.trace({ secretAccessKey }, 's3: [S3PackageManager constructor] secretAccessKey @{secretAccessKey}');\n this.logger.trace({ sessionToken }, 's3: [S3PackageManager constructor] sessionToken @{sessionToken}');\n\n const packageAccess = this.config.getMatchedPackagesSpec(packageName);\n if (packageAccess) {\n const storage = packageAccess.storage;\n const packageCustomFolder = addTrailingSlash(storage);\n this.packagePath = `${this.config.keyPrefix}${packageCustomFolder}${this.packageName}`;\n } else {\n this.packagePath = `${this.config.keyPrefix}${this.packageName}`;\n }\n }\n\n public updatePackage(\n name: string,\n updateHandler: Callback,\n onWrite: Callback,\n transformPackage: Function,\n onEnd: Callback\n ): void {\n this.logger.debug({ name }, 's3: [S3PackageManager updatePackage init] @{name}');\n (async (): Promise => {\n try {\n const json = await this._getData();\n updateHandler(json, err => {\n if (err) {\n this.logger.error({ err }, 's3: [S3PackageManager updatePackage updateHandler onEnd] @{err}');\n onEnd(err);\n } else {\n const transformedPackage = transformPackage(json);\n this.logger.debug(\n { transformedPackage },\n 's3: [S3PackageManager updatePackage updateHandler onWrite] @{transformedPackage}'\n );\n onWrite(name, transformedPackage, onEnd);\n }\n });\n } catch (err) {\n this.logger.error({ err }, 's3: [S3PackageManager updatePackage updateHandler onEnd catch] @{err}');\n\n return onEnd(err);\n }\n })();\n }\n\n private async _getData(): Promise {\n this.logger.debug('s3: [S3PackageManager _getData init]');\n return await new Promise((resolve, reject): void => {\n this.s3.getObject(\n {\n Bucket: this.config.bucket,\n Key: `${this.packagePath}/${pkgFileName}`,\n },\n (err, response) => {\n if (err) {\n this.logger.error({ err: err.message }, 's3: [S3PackageManager _getData] aws @{err}');\n const error: HttpError = convertS3Error(err);\n this.logger.error({ error: err.message }, 's3: [S3PackageManager _getData] @{error}');\n\n reject(error);\n return;\n }\n const body = response.Body ? response.Body.toString() : '';\n let data;\n try {\n data = JSON.parse(body);\n } catch (e) {\n this.logger.error({ body }, 's3: [S3PackageManager _getData] error parsing: @{body}');\n reject(e);\n return;\n }\n\n this.logger.trace({ data }, 's3: [S3PackageManager _getData body] @{data.name}');\n resolve(data);\n }\n );\n });\n }\n\n public deletePackage(fileName: string, callback: Callback): void {\n this.s3.deleteObject(\n {\n Bucket: this.config.bucket,\n Key: `${this.packagePath}/${fileName}`,\n },\n err => {\n if (err) {\n callback(err);\n } else {\n callback(null);\n }\n }\n );\n }\n\n public removePackage(callback: CallbackAction): void {\n deleteKeyPrefix(\n this.s3,\n {\n Bucket: this.config.bucket,\n Prefix: addTrailingSlash(this.packagePath),\n },\n function(err) {\n if (err && is404Error(err as VerdaccioError)) {\n callback(null);\n } else {\n callback(err);\n }\n }\n );\n }\n\n public createPackage(name: string, value: Package, callback: CallbackAction): void {\n this.logger.debug(\n { name, packageName: this.packageName },\n 's3: [S3PackageManager createPackage init] name @{name}/@{packageName}'\n );\n this.logger.trace({ value }, 's3: [S3PackageManager createPackage init] name @value');\n this.s3.headObject(\n {\n Bucket: this.config.bucket,\n Key: `${this.packagePath}/${pkgFileName}`,\n },\n (err, data) => {\n if (err) {\n const s3Err = convertS3Error(err);\n // only allow saving if this file doesn't exist already\n if (is404Error(s3Err)) {\n this.logger.debug({ s3Err }, 's3: [S3PackageManager createPackage] 404 package not found]');\n this.savePackage(name, value, callback);\n this.logger.trace({ data }, 's3: [S3PackageManager createPackage] package saved data from s3: @{data}');\n } else {\n this.logger.error({ s3Err: s3Err.message }, 's3: [S3PackageManager createPackage error] @s3Err');\n callback(s3Err);\n }\n } else {\n this.logger.debug('s3: [S3PackageManager createPackage ] package exist already');\n callback(create409Error());\n }\n }\n );\n }\n\n public savePackage(name: string, value: Package, callback: CallbackAction): void {\n this.logger.debug(\n { name, packageName: this.packageName },\n 's3: [S3PackageManager savePackage init] name @{name}/@{packageName}'\n );\n this.logger.trace({ value }, 's3: [S3PackageManager savePackage ] init value @{value}');\n this.s3.putObject(\n {\n // TODO: not sure whether save the object with spaces will increase storage size\n Body: JSON.stringify(value, null, ' '),\n Bucket: this.config.bucket,\n Key: `${this.packagePath}/${pkgFileName}`,\n ACL: this.config.acl,\n },\n callback\n );\n }\n\n public readPackage(name: string, callback: ReadPackageCallback): void {\n this.logger.debug(\n { name, packageName: this.packageName },\n 's3: [S3PackageManager readPackage init] name @{name}/@{packageName}'\n );\n (async (): Promise => {\n try {\n const data: Package = (await this._getData()) as Package;\n this.logger.trace(\n { data, packageName: this.packageName },\n 's3: [S3PackageManager readPackage] packageName: @{packageName} / data @{data}'\n );\n callback(null, data);\n } catch (err: any) {\n this.logger.error({ err: err.message }, 's3: [S3PackageManager readPackage] @{err}');\n callback(err);\n }\n })();\n }\n\n public writeTarball(name: string): UploadTarball {\n this.logger.debug(\n { name, packageName: this.packageName },\n 's3: [S3PackageManager writeTarball init] name @{name}/@{packageName}'\n );\n const uploadStream = new UploadTarball({});\n\n let streamEnded = 0;\n uploadStream.on('end', () => {\n this.logger.debug(\n { name, packageName: this.packageName },\n 's3: [S3PackageManager writeTarball event: end] name @{name}/@{packageName}'\n );\n streamEnded = 1;\n });\n\n const baseS3Params = {\n Bucket: this.config.bucket,\n Key: `${this.packagePath}/${name}`,\n };\n\n // NOTE: I'm using listObjectVersions so I don't have to download the full object with getObject.\n // Preferably, I'd use getObjectMetadata or getDetails when it's available in the node sdk\n // TODO: convert to headObject\n this.s3.headObject(\n {\n Bucket: this.config.bucket,\n Key: `${this.packagePath}/${name}`,\n },\n err => {\n if (err) {\n const convertedErr = convertS3Error(err);\n this.logger.error({ error: convertedErr.message }, 's3: [S3PackageManager writeTarball headObject] @{error}');\n\n if (is404Error(convertedErr) === false) {\n this.logger.error(\n {\n error: convertedErr.message,\n },\n 's3: [S3PackageManager writeTarball headObject] non a 404 emit error: @{error}'\n );\n\n uploadStream.emit('error', convertedErr);\n } else {\n this.logger.debug('s3: [S3PackageManager writeTarball managedUpload] init stream');\n const managedUpload = this.s3.upload(\n Object.assign({}, baseS3Params, { Body: uploadStream, ACL: this.tarballACL })\n );\n // NOTE: there's a managedUpload.promise, but it doesn't seem to work\n const promise = new Promise((resolve): void => {\n this.logger.debug('s3: [S3PackageManager writeTarball managedUpload] send');\n managedUpload.send((err, data) => {\n if (err) {\n const error: HttpError = convertS3Error(err);\n this.logger.error(\n { error: error.message },\n 's3: [S3PackageManager writeTarball managedUpload send] emit error @{error}'\n );\n\n uploadStream.emit('error', error);\n } else {\n this.logger.trace(\n { data },\n 's3: [S3PackageManager writeTarball managedUpload send] response @{data}'\n );\n\n resolve();\n }\n });\n\n this.logger.debug({ name }, 's3: [S3PackageManager writeTarball uploadStream] emit open @{name}');\n uploadStream.emit('open');\n });\n\n uploadStream.done = (): void => {\n const onEnd = async (): Promise => {\n try {\n await promise;\n\n this.logger.debug('s3: [S3PackageManager writeTarball uploadStream done] emit success');\n uploadStream.emit('success');\n } catch (err) {\n // already emitted in the promise above, necessary because of some issues\n // with promises in jest\n this.logger.error({ err }, 's3: [S3PackageManager writeTarball uploadStream done] error @{err}');\n }\n };\n if (streamEnded) {\n this.logger.trace(\n { name },\n 's3: [S3PackageManager writeTarball uploadStream] streamEnded true @{name}'\n );\n onEnd();\n } else {\n this.logger.trace(\n { name },\n 's3: [S3PackageManager writeTarball uploadStream] streamEnded false emit end @{name}'\n );\n uploadStream.on('end', onEnd);\n }\n };\n\n uploadStream.abort = (): void => {\n this.logger.debug('s3: [S3PackageManager writeTarball uploadStream abort] init');\n try {\n this.logger.debug('s3: [S3PackageManager writeTarball managedUpload abort]');\n managedUpload.abort();\n } catch (err: any) {\n const error: HttpError = convertS3Error(err);\n uploadStream.emit('error', error);\n\n this.logger.error(\n { error },\n 's3: [S3PackageManager writeTarball uploadStream error] emit error @{error}'\n );\n } finally {\n this.logger.debug(\n { name, baseS3Params },\n 's3: [S3PackageManager writeTarball uploadStream abort] s3.deleteObject @{name}/@{baseS3Params}'\n );\n\n this.s3.deleteObject(baseS3Params);\n }\n };\n }\n } else {\n this.logger.debug({ name }, 's3: [S3PackageManager writeTarball headObject] emit error @{name} 409');\n\n uploadStream.emit('error', create409Error());\n }\n }\n );\n\n return uploadStream;\n }\n\n public readTarball(name: string): ReadTarball {\n this.logger.debug(\n { name, packageName: this.packageName },\n 's3: [S3PackageManager readTarball init] name @{name}/@{packageName}'\n );\n const readTarballStream = new ReadTarball({});\n\n const request = this.s3.getObject({\n Bucket: this.config.bucket,\n Key: `${this.packagePath}/${name}`,\n });\n\n let headersSent = false;\n\n const readStream = request\n .on('httpHeaders', (statusCode, headers) => {\n // don't process status code errors here, we'll do that in readStream.on('error'\n // otherwise they'll be processed twice\n\n // verdaccio force garbage collects a stream on 404, so we can't emit more\n // than one error or it'll fail\n // https://github.com/verdaccio/verdaccio/blob/c1bc261/src/lib/storage.js#L178\n this.logger.debug(\n { name, packageName: this.packageName },\n 's3: [S3PackageManager readTarball httpHeaders] name @{name}/@{packageName}'\n );\n this.logger.trace({ headers }, 's3: [S3PackageManager readTarball httpHeaders event] headers @headers');\n this.logger.trace(\n { statusCode },\n 's3: [S3PackageManager readTarball httpHeaders event] statusCode @{statusCode}'\n );\n if (statusCode !== HTTP_STATUS.NOT_FOUND) {\n if (headers[HEADERS.CONTENT_LENGTH]) {\n const contentLength = parseInt(headers[HEADERS.CONTENT_LENGTH], 10);\n\n // not sure this is necessary\n if (headersSent) {\n return;\n }\n\n headersSent = true;\n\n this.logger.debug('s3: [S3PackageManager readTarball readTarballStream event] emit content-length');\n readTarballStream.emit(HEADERS.CONTENT_LENGTH, contentLength);\n // we know there's content, so open the stream\n readTarballStream.emit('open');\n this.logger.debug('s3: [S3PackageManager readTarball readTarballStream event] emit open');\n }\n } else {\n this.logger.trace('s3: [S3PackageManager readTarball httpHeaders event] not found, avoid emit open file');\n }\n })\n .createReadStream();\n\n readStream.on('error', err => {\n const error: HttpError = convertS3Error(err as AWSError);\n\n readTarballStream.emit('error', error);\n this.logger.error(\n { error: error.message },\n 's3: [S3PackageManager readTarball readTarballStream event] error @{error}'\n );\n });\n\n this.logger.trace('s3: [S3PackageManager readTarball readTarballStream event] pipe');\n readStream.pipe(readTarballStream);\n\n readTarballStream.abort = (): void => {\n this.logger.debug('s3: [S3PackageManager readTarball readTarballStream event] request abort');\n request.abort();\n this.logger.debug('s3: [S3PackageManager readTarball readTarballStream event] request destroy');\n readStream.destroy();\n };\n\n return readTarballStream;\n }\n}\n"],"file":"s3PackageManager.js"} \ No newline at end of file diff --git a/lib/setConfigValue.d.ts b/lib/setConfigValue.d.ts new file mode 100644 index 0000000..cbb1d68 --- /dev/null +++ b/lib/setConfigValue.d.ts @@ -0,0 +1,2 @@ +declare const _default: (configValue: any) => string; +export default _default; diff --git a/lib/setConfigValue.js b/lib/setConfigValue.js new file mode 100644 index 0000000..1def247 --- /dev/null +++ b/lib/setConfigValue.js @@ -0,0 +1,14 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _default = configValue => { + const envValue = process.env[configValue]; + return envValue || configValue; +}; + +exports.default = _default; +//# sourceMappingURL=setConfigValue.js.map \ No newline at end of file diff --git a/lib/setConfigValue.js.map b/lib/setConfigValue.js.map new file mode 100644 index 0000000..09ce21d --- /dev/null +++ b/lib/setConfigValue.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/setConfigValue.ts"],"names":["configValue","envValue","process","env"],"mappings":";;;;;;;eAAgBA,WAAD,IAA8B;AAC3C,QAAMC,QAAQ,GAAGC,OAAO,CAACC,GAAR,CAAYH,WAAZ,CAAjB;AACA,SAAOC,QAAQ,IAAID,WAAnB;AACD,C","sourcesContent":["export default (configValue: any): string => {\n const envValue = process.env[configValue];\n return envValue || configValue;\n};\n"],"file":"setConfigValue.js"} \ No newline at end of file diff --git a/package.json b/package.json index 7f5eb4c..58ee11e 100644 --- a/package.json +++ b/package.json @@ -28,12 +28,13 @@ "node": ">=10" }, "dependencies": { - "@verdaccio/commons-api": "workspace:10.1.0", - "@verdaccio/streams": "workspace:10.1.0", + "@verdaccio/commons-api": "10.1.0", + "@verdaccio/streams": "10.1.0", "aws-sdk": "^2.607.0" }, "devDependencies": { - "@verdaccio/types": "workspace:10.2.2", + "@verdaccio/types": "10.2.2", + "pnpm": "^6.28.0", "recursive-readdir": "2.2.2" }, "scripts": { diff --git a/src/s3PackageManager.ts b/src/s3PackageManager.ts index c61b77e..c00c2f5 100644 --- a/src/s3PackageManager.ts +++ b/src/s3PackageManager.ts @@ -210,7 +210,7 @@ export default class S3PackageManager implements ILocalPackageManager { 's3: [S3PackageManager readPackage] packageName: @{packageName} / data @{data}' ); callback(null, data); - } catch (err) { + } catch (err: any) { this.logger.error({ err: err.message }, 's3: [S3PackageManager readPackage] @{err}'); callback(err); } @@ -266,7 +266,7 @@ export default class S3PackageManager implements ILocalPackageManager { Object.assign({}, baseS3Params, { Body: uploadStream, ACL: this.tarballACL }) ); // NOTE: there's a managedUpload.promise, but it doesn't seem to work - const promise = new Promise((resolve): void => { + const promise = new Promise((resolve): void => { this.logger.debug('s3: [S3PackageManager writeTarball managedUpload] send'); managedUpload.send((err, data) => { if (err) { @@ -324,7 +324,7 @@ export default class S3PackageManager implements ILocalPackageManager { try { this.logger.debug('s3: [S3PackageManager writeTarball managedUpload abort]'); managedUpload.abort(); - } catch (err) { + } catch (err: any) { const error: HttpError = convertS3Error(err); uploadStream.emit('error', error);