Skip to content

Commit

Permalink
Support azure storage (#221)
Browse files Browse the repository at this point in the history
* azure storage

* azure uploader

* azure uploader

* azure uploader

* fix
  • Loading branch information
xquanluu authored Aug 22, 2023
1 parent 042ad9f commit 9d24ef6
Show file tree
Hide file tree
Showing 7 changed files with 478 additions and 19 deletions.
41 changes: 41 additions & 0 deletions lib/record/azure-storage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const { Writable } = require('stream');
const { BlobServiceClient } = require('@azure/storage-blob');
const { v4: uuidv4 } = require('uuid');

class AzureStorageUploadStream extends Writable {
constructor(logger, opts) {
super(opts);
const blobServiceClient = BlobServiceClient.fromConnectionString(opts.connection_string);
this.blockBlobClient = blobServiceClient.getContainerClient(opts.bucketName).getBlockBlobClient(opts.Key);
this.metadata = opts.metadata;
this.blocks = [];
}

async _write(chunk, encoding, callback) {
const blockID = uuidv4().replace(/-/g, '');
this.blocks.push(blockID);
try {
await this.blockBlobClient.stageBlock(blockID, chunk, chunk.length);
callback();
} catch (error) {
callback(error);
}
}

async _final(callback) {
try {
await this.blockBlobClient.commitBlockList(this.blocks);
// remove all null/undefined props
const filteredObj = Object.entries(this.metadata).reduce((acc, [key, val]) => {
if (val !== undefined && val !== null) acc[key] = val;
return acc;
}, {});
await this.blockBlobClient.setMetadata(filteredObj);
callback();
} catch (error) {
callback(error);
}
}
}

module.exports = AzureStorageUploadStream;
4 changes: 4 additions & 0 deletions lib/record/utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const AzureStorageUploadStream = require('./azure-storage');
const GoogleStorageUploadStream = require('./google-storage');
const S3MultipartUploadStream = require('./s3-multipart-upload-stream');

Expand Down Expand Up @@ -27,6 +28,9 @@ const getUploader = (Key, metadata, bucket_credential, logger) => {
}
};
return new GoogleStorageUploadStream(logger, uploaderOpts);
case 'azure':
uploaderOpts.connection_string = bucket_credential.connection_string;
return new AzureStorageUploadStream(logger, uploaderOpts);

default:
logger.error(`unknown bucket vendor: ${bucket_credential.vendor}`);
Expand Down
17 changes: 14 additions & 3 deletions lib/routes/api/accounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const {
const short = require('short-uuid');
const VoipCarrier = require('../../models/voip-carrier');
const { encrypt } = require('../../utils/encrypt-decrypt');
const { testAwsS3, testGoogleStorage } = require('../../utils/storage-utils');
const { testAwsS3, testGoogleStorage, testAzureStorage } = require('../../utils/storage-utils');
const translator = short();

let idx = 0;
Expand Down Expand Up @@ -542,7 +542,8 @@ function encryptBucketCredential(obj) {
access_key_id,
secret_access_key,
tags,
service_key
service_key,
connection_string
} = obj.bucket_credential;

switch (vendor) {
Expand All @@ -560,6 +561,12 @@ function encryptBucketCredential(obj) {
const googleData = JSON.stringify({vendor, name, service_key, tags});
obj.bucket_credential = encrypt(googleData);
break;
case 'azure':
assert(name, 'invalid azure container name: name is required');
assert(connection_string, 'invalid azure cloud storage credential: connection_string is required');
const azureData = JSON.stringify({vendor, name, connection_string, tags});
obj.bucket_credential = encrypt(azureData);
break;
case 'none':
obj.bucket_credential = null;
break;
Expand Down Expand Up @@ -714,7 +721,7 @@ router.post('/:sid/BucketCredentialTest', async(req, res) => {
try {
const account_sid = parseAccountSid(req);
await validateRequest(req, account_sid);
const {vendor, name, region, access_key_id, secret_access_key, service_key} = req.body;
const {vendor, name, region, access_key_id, secret_access_key, service_key, connection_string} = req.body;
const ret = {
status: 'not tested'
};
Expand All @@ -728,6 +735,10 @@ router.post('/:sid/BucketCredentialTest', async(req, res) => {
await testGoogleStorage(logger, {vendor, name, service_key});
ret.status = 'ok';
break;
case 'azure':
await testAzureStorage(logger, {vendor, name, connection_string});
ret.status = 'ok';
break;
default:
throw new DbErrorBadRequest(`Does not support test for ${vendor}`);
}
Expand Down
5 changes: 4 additions & 1 deletion lib/routes/api/recent-calls.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const {DbErrorBadRequest} = require('../../utils/errors');
const {getHomerApiKey, getHomerSipTrace, getHomerPcap} = require('../../utils/homer-utils');
const {getJaegerTrace} = require('../../utils/jaeger-utils');
const Account = require('../../models/account');
const { getS3Object, getGoogleStorageObject } = require('../../utils/storage-utils');
const { getS3Object, getGoogleStorageObject, getAzureStorageObject } = require('../../utils/storage-utils');

const parseAccountSid = (url) => {
const arr = /Accounts\/([^\/]*)/.exec(url);
Expand Down Expand Up @@ -136,6 +136,9 @@ router.get('/:call_sid/record/:year/:month/:day/:format', async(req, res) => {
case 'google':
stream = await getGoogleStorageObject(logger, getOptions);
break;
case 'azure':
stream = await getAzureStorageObject(logger, getOptions);
break;
default:
logger.error(`There is no handler for fetching record from ${bucket_credential.vendor}`);
return res.sendStatus(500);
Expand Down
21 changes: 20 additions & 1 deletion lib/utils/storage-utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
const { S3Client, PutObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3');
const {Storage} = require('@google-cloud/storage');
const fs = require('fs');
const { BlobServiceClient } = require('@azure/storage-blob');

async function testAzureStorage(logger, opts) {
const blobServiceClient = BlobServiceClient.fromConnectionString(opts.connection_string);
const containerClient = blobServiceClient.getContainerClient(opts.name);
const blockBlobClient = containerClient.getBlockBlobClient('jambonz-sample.text');

await blockBlobClient.uploadFile(`${__dirname}/jambonz-sample.text`);
}

async function getAzureStorageObject(logger, opts) {
const blobServiceClient = BlobServiceClient.fromConnectionString(opts.connection_string);
const containerClient = blobServiceClient.getContainerClient(opts.name);
const blockBlobClient = containerClient.getBlockBlobClient(opts.key);
const response = await blockBlobClient.download(0);
return response.readableStreamBody;
}

function testGoogleStorage(logger, opts) {
return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -78,5 +95,7 @@ module.exports = {
testAwsS3,
getS3Object,
testGoogleStorage,
getGoogleStorageObject
getGoogleStorageObject,
testAzureStorage,
getAzureStorageObject
};
Loading

0 comments on commit 9d24ef6

Please sign in to comment.