lemon-core
Version:
Lemon Serverless Micro-Service Platform
431 lines • 21 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BlobService = void 0;
/**
* `s3s-service.js`
* - common S3 services.
*
*
* @author Ian Kim <ian@lemoncloud.io>
* @date 2023-09-18 initial azure blob service
*
* @copyright (C) lemoncloud.io 2023 - All Rights Reserved.
*/
/** ****************************************************************************************************************
* Common Headers
** ****************************************************************************************************************/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const engine_1 = require("../../engine");
const uuid_1 = require("uuid");
const test_helper_1 = require("../../common/test-helper");
// import { KeyVaultService } from './azure-keyvault-service';
require("dotenv/config");
const NS = engine_1.$U.NS('BLOB', 'blue');
const instance = () => {
return BlobService.instance();
};
/** ****************************************************************************************************************
* Public Instance Exported.
** ****************************************************************************************************************/
const region = () => engine_1.$engine.environ('REGION', 'koreacentral');
/**
* use `target` as value or environment value.
* environ('abc') => string 'abc'
* environ('ABC') => use `env.ABC`
*/
const environ = (target, defEnvName, defEnvValue) => {
const isUpperStr = target && /^[A-Z][A-Z0-9_]+$/.test(target);
defEnvName = isUpperStr ? target : defEnvName;
const val = defEnvName ? engine_1.$engine.environ(defEnvName, defEnvValue) : defEnvValue;
target = isUpperStr ? '' : target;
return `${target || val}`;
};
/**
* main service implement.
*/
class BlobService {
constructor() {
// protected $kv: KeyVaultService;
// constructor() {
// this.$kv = new KeyVaultService();
// }
/**
* get name of this
*/
this.name = () => `BLOB`;
/**
* hello
*/
this.hello = () => `azure-blob-service:${this.bucket()}`;
/**
* get target endpoint by name.
*/
this.bucket = (target) => environ(target, BlobService.ENV_BLOB_NAME, BlobService.DEF_BLOB_BUCKET);
/**
* get azure sdk for blob
*/
// public static $kv: KeyVaultService = new KeyVaultService();
this.instance = () => __awaiter(this, void 0, void 0, function* () {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { BlobServiceClient, StorageSharedKeyCredential, BlobItem, Metadata } = require('@azure/storage-blob');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { StorageManagementClient } = require('@azure/arm-storage');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { DefaultAzureCredential } = require('@azure/identity');
const account = process.env.STORAGE_ACCOUNT_RESOURCE;
const accountKey = process.env.STORAGE_ACCOUNT_ACCESS_KEY;
// const account = await BlobService.$kv.decrypt(process.env.STORAGE_ACCOUNT_RESOURCE);
// const accountKey = await BlobService.$kv.decrypt(process.env.STORAGE_ACCOUNT_ACCESS_KEY);
const subscriptionId = process.env.SUBSCRIPTION_ID;
const resourceGroupName = process.env.RESOURCE_GROUP;
const sharedKeyCredential = new StorageSharedKeyCredential(account, accountKey);
const blobServiceClient = new BlobServiceClient(`https://${account}.blob.core.windows.net`, sharedKeyCredential);
const storageClient = new StorageManagementClient(new DefaultAzureCredential(), subscriptionId);
return { storageClient, blobServiceClient, resourceGroupName, Metadata, BlobItem };
});
/**
* retrieve metadata without returning the object
*
* @param {string} key
* @return metadata object / null if not exists
*/
this.headObject = (key) => __awaiter(this, void 0, void 0, function* () {
if (!key)
throw new Error(`@key (string) is required - headObject(${key !== null && key !== void 0 ? key : ''})`);
const { blobServiceClient } = yield this.instance();
const Bucket = this.bucket();
const params = { Bucket, Key: key };
const parts = key.split('/');
const fileName = parts[parts.length - 1];
const containerClient = blobServiceClient.getContainerClient(Bucket);
const blobClient = containerClient.getBlobClient(fileName);
try {
const data = yield blobClient.getProperties();
(0, engine_1._log)(NS, '> data =', engine_1.$U.json(Object.assign(Object.assign({}, data), { Contents: undefined })));
const result = {
ContentType: data.contentType,
ContentLength: data.contentLength,
Metadata: data.metadata,
ETag: data.etag,
LastModified: engine_1.$U.ts(data.lastModified),
};
return result;
}
catch (e) {
if (e.statusCode == 404)
return null;
(0, engine_1._err)(NS, '! err=', e);
throw e;
}
});
/**
* get a file from Blob Container
*
* @param {string} key
*/
this.getObject = (key) => __awaiter(this, void 0, void 0, function* () {
if (!key)
throw new Error(`@key (string) is required - getObject(${key !== null && key !== void 0 ? key : ''})`);
const { blobServiceClient, Metadata } = yield this.instance();
const Bucket = this.bucket();
const params = { Bucket, Key: key };
const parts = key.split('/');
const fileName = parts[parts.length - 1];
const containerClient = blobServiceClient.getContainerClient(Bucket);
const blobClient = containerClient.getBlobClient(fileName);
const blockBlobClient = containerClient.getBlockBlobClient(fileName);
function getJsonData(blobResponse) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
const chunks = [];
blobResponse.blobDownloadStream.on('data', (chunk) => {
chunks.push(chunk);
});
blobResponse.blobDownloadStream.on('end', () => {
try {
const jsonData = JSON.parse(Buffer.concat(chunks).toString('utf8'));
resolve(jsonData);
}
catch (error) {
reject(error);
}
});
blobResponse.blobDownloadStream.on('error', (error) => {
reject(error);
});
});
});
}
try {
const _data = yield blobClient.download();
const data = yield getJsonData(_data);
const properties = yield blobClient.getProperties();
const ContentType = properties.contentType;
const ContentLength = properties.contentLength;
const Metadata = properties.metadata;
const ETag = properties.etag;
const tagResponse = yield blockBlobClient.getTags();
(0, engine_1._log)(NS, '> data.type =', typeof data);
const Body = JSON.stringify(data);
const result = { ContentLength, ContentType, Body, ETag, Metadata };
if (tagResponse)
result.TagCount = Object.keys(tagResponse.tags).length;
return result;
}
catch (e) {
(0, engine_1._err)(NS, '! err=', e);
throw e;
}
});
/**
* return decoded Object from Blob Container file.
*
* @param {string} key ex) 'hello-0001.json' , 'dist/hello-0001.json
*/
this.getDecodedObject = (key) => __awaiter(this, void 0, void 0, function* () {
if (!key)
throw new Error(`@key (string) is required - getDecodedObject(${key !== null && key !== void 0 ? key : ''})`);
const Bucket = this.bucket();
const params = { Bucket, Key: key };
try {
const data = yield this.getObject(key).catch(e => {
(0, engine_1._log)(NS, '> data.type =', typeof data);
(0, engine_1._err)(NS, '! err=', e);
throw e;
});
if (!data) {
throw new Error('Data not found');
}
const content = data.Body.toString();
return JSON.parse(content);
}
catch (e) {
(0, engine_1._err)(NS, '! err=', e);
throw e;
}
});
/**
* get tag-set of object
*
* @param {string} key
*/
this.getObjectTagging = (key) => __awaiter(this, void 0, void 0, function* () {
if (!key)
throw new Error(`@key (string) is required - getObjectTagging(${key !== null && key !== void 0 ? key : ''})`);
const { blobServiceClient } = yield this.instance();
const Bucket = this.bucket();
const params = { Bucket, Key: key };
const parts = key.split('/');
const fileName = parts[parts.length - 1];
const containerClient = blobServiceClient.getContainerClient(Bucket);
const blobClient = containerClient.getBlobClient(fileName);
try {
const data = yield blobClient.getTags();
(0, engine_1._log)(NS, `> data =`, engine_1.$U.json(data));
return data === null || data === void 0 ? void 0 : data.tags;
}
catch (e) {
(0, engine_1._err)(NS, '! err=', e);
throw e;
}
});
/**
* delete object from Blob Container
*
* @param {string} key
*/
this.deleteObject = (key) => __awaiter(this, void 0, void 0, function* () {
if (!key)
throw new Error(`@key (string) is required - deleteObject(${key !== null && key !== void 0 ? key : ''})`);
const { blobServiceClient } = yield this.instance();
const Bucket = this.bucket();
const params = { Bucket, Key: key };
const parts = key.split('/');
const fileName = parts[parts.length - 1];
const containerClient = blobServiceClient.getContainerClient(Bucket);
const blobClient = containerClient.getBlobClient(fileName);
try {
const data = yield blobClient.delete();
(0, engine_1._log)(NS, '> data =', engine_1.$U.json(data));
}
catch (e) {
(0, engine_1._err)(NS, '! err=', e);
throw e;
}
});
/**
* list objects in Blob Container
*/
this.listObjects = (options) => __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
// if (!key) throw new Error('@key is required!');
const Prefix = (_a = options === null || options === void 0 ? void 0 : options.prefix) !== null && _a !== void 0 ? _a : '';
const Delimiter = (_b = options === null || options === void 0 ? void 0 : options.delimiter) !== null && _b !== void 0 ? _b : '/';
const MaxKeys = Math.min((_c = options === null || options === void 0 ? void 0 : options.limit) !== null && _c !== void 0 ? _c : 10, 1000);
const unlimited = (_d = options === null || options === void 0 ? void 0 : options.unlimited) !== null && _d !== void 0 ? _d : false;
const nextToken = options === null || options === void 0 ? void 0 : options.nextToken;
const throwable = (_e = options === null || options === void 0 ? void 0 : options.throwable) !== null && _e !== void 0 ? _e : true;
//* build the req-params.
const Bucket = this.bucket();
const { blobServiceClient } = yield this.instance();
const containerClient = blobServiceClient.getContainerClient(Bucket);
const result = {
Contents: [],
MaxKeys,
KeyCount: 0,
IsTruncated: false,
};
try {
let count = 0;
result.MaxKeys = MaxKeys;
let iterator = containerClient.listBlobsFlat().byPage(Object.assign({ maxPageSize: MaxKeys }, (nextToken ? { continuationToken: nextToken } : {})));
let response = (yield iterator.next()).value;
if (response !== undefined &&
(response === null || response === void 0 ? void 0 : response.segment) !== undefined &&
((_f = response === null || response === void 0 ? void 0 : response.segment) === null || _f === void 0 ? void 0 : _f.blobItems) !== undefined) {
for (const blob of (_g = response === null || response === void 0 ? void 0 : response.segment) === null || _g === void 0 ? void 0 : _g.blobItems) {
result.Contents.push({
Key: blob.name,
Size: blob.properties.contentLength,
});
}
count++;
result.NextContinuationToken = response === null || response === void 0 ? void 0 : response.continuationToken;
if (!unlimited) {
result.KeyCount = count;
result.IsTruncated = true;
return result;
}
}
while (response !== undefined &&
(response === null || response === void 0 ? void 0 : response.segment) !== undefined &&
((_h = response === null || response === void 0 ? void 0 : response.segment) === null || _h === void 0 ? void 0 : _h.blobItems) !== undefined) {
iterator = containerClient.listBlobsFlat().byPage({
maxPageSize: MaxKeys,
continuationToken: result.NextContinuationToken,
});
response = (yield iterator.next()).value;
result.NextContinuationToken = response === null || response === void 0 ? void 0 : response.continuationToken;
if (((_j = response === null || response === void 0 ? void 0 : response.segment) === null || _j === void 0 ? void 0 : _j.blobItems.length) > 0 &&
response !== undefined &&
(response === null || response === void 0 ? void 0 : response.segment) !== undefined &&
((_k = response === null || response === void 0 ? void 0 : response.segment) === null || _k === void 0 ? void 0 : _k.blobItems) !== undefined) {
for (const blob of response.segment.blobItems) {
result.Contents.push({
Key: blob.name,
Size: blob.properties.contentLength,
});
}
count++;
}
}
result.KeyCount = count;
}
catch (e) {
(0, engine_1._err)(NS, '! err=', e);
if (throwable)
throw e;
result.error = (0, test_helper_1.GETERR)(e);
}
return result;
});
/**
* upload a file to Blob Container
*
*
* @param {string|Buffer} content content body
* @param {string} key (optional) S3 key to put
* @param {Metadata} metadata (optional) metadata to store
* @param {object} tags (optional) tag set
*/
this.putObject = (content, key, metadata, tags) => __awaiter(this, void 0, void 0, function* () {
if (!content)
throw new Error(`@content (buffer) is required - putObject()`);
function generateBlobName() {
const uuid = (0, uuid_1.v4)();
return `${uuid}.json`;
}
if (!key)
key = generateBlobName();
const { blobServiceClient, storageClient, resourceGroupName } = yield this.instance();
const Bucket = this.bucket();
const parts = key.split('/');
const fileName = parts[parts.length - 1];
const containerClient = blobServiceClient.getContainerClient(Bucket);
const blobClient = containerClient.getBlobClient(fileName);
//* upsert
const blobExists = yield blobClient.exists();
if (blobExists) {
}
else {
const blockBlobClient = containerClient.getBlockBlobClient(fileName);
yield blockBlobClient.upload(content, content.length, {
blobHTTPHeaders: {
blobContentType: 'application/json; charset=utf-8',
},
});
}
//* metadata has ContentType
if (metadata && metadata.hasOwnProperty('ContentType')) {
yield blobClient.setHTTPHeaders({
blobContentType: metadata.ContentType,
});
}
const properties = yield blobClient.getProperties();
const contentType = properties.contentType;
const contentLength = properties.contentLength;
const eTag = properties.etag;
(0, engine_1._log)(NS, `> params.ContentType =`, contentType);
(0, engine_1._log)(NS, `> params.ContentLength =`, contentLength);
(0, engine_1._log)(NS, `> params.Metadata =`, metadata);
(0, engine_1._log)(NS, `> params.Tagging =`, eTag);
try {
const blockBlobClient = containerClient.getBlockBlobClient(fileName);
yield blockBlobClient.upload(content, content.length, {
blobHTTPHeaders: {
blobContentType: 'application/json; charset=utf-8',
},
});
const storageAccount = yield storageClient.storageAccounts.getProperties(resourceGroupName, blobClient.accountName);
if (metadata)
yield blobClient.setMetadata(metadata);
if (tags)
yield blobClient.setTags(tags);
const result = {
Bucket: Bucket,
Location: storageAccount.location,
Key: key,
ETag: eTag,
ContentType: contentType,
ContentLength: contentLength,
Metadata: metadata,
};
return result;
}
catch (e) {
(0, engine_1._err)(NS, `! err[${Bucket}] =`, e);
throw e;
}
});
}
}
exports.BlobService = BlobService;
/**
* environ name to use `bucket`
*/
BlobService.ENV_BLOB_NAME = 'my-blob-container';
/**
* default `bucket` name
*/
BlobService.DEF_BLOB_BUCKET = (_a = process.env.BLOB_CONTAINER) !== null && _a !== void 0 ? _a : 'blob-container';
//# sourceMappingURL=azure-blob-service.js.map