Skip to content

Commit 37d145f

Browse files
committed
# This is a combination of 3 commits.
# The first commit's message is: Added FilesAdapter for Azure Blob Storage # This is the 2nd commit message: Updated Azure Blob Adapter to return Buffer instead of text # This is the 3rd commit message: Added tests for Azure Blob Storage Added an extra "mount" parameter in FilesControllerTestFactory
1 parent 930e842 commit 37d145f

File tree

5 files changed

+141
-1
lines changed

5 files changed

+141
-1
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"apn": "^1.7.5",
2222
"aws-sdk": "~2.2.33",
2323
"babel-polyfill": "^6.5.0",
24+
"azure-storage": "^0.8.0",
2425
"babel-runtime": "^6.5.0",
2526
"bcrypt-nodejs": "0.0.3",
2627
"body-parser": "^1.14.2",

spec/FilesController.spec.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
var FilesController = require('../src/Controllers/FilesController').FilesController;
22
var GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter;
33
var S3Adapter = require("../src/Adapters/Files/S3Adapter").S3Adapter;
4+
var AzureBlobStorageAdapter = require("../src/Adapters/Files/AzureBlobStorageAdapter").AzureBlobStorageAdapter;
45
var Config = require("../src/Config");
56

67
var FCTestFactory = require("./FilesControllerTestFactory");
@@ -30,4 +31,18 @@ describe("FilesController",()=>{
3031
} else if (!process.env.TRAVIS) {
3132
console.log("set S3_ACCESS_KEY and S3_SECRET_KEY to test S3Adapter")
3233
}
34+
if (process.env.AZURE_STORAGE_ACCOUNT_NAME && process.env.AZURE_STORAGE_ACCOUNT_KEY) {
35+
// Test the Azure Blob Storage Adapter
36+
var azureBlobStorageAdapter = new AzureBlobStorageAdapter(process.env.AZURE_STORAGE_ACCOUNT_NAME, 'parseservertests', { storageAccessKey: process.env.AZURE_STORAGE_ACCOUNT_KEY });
37+
38+
FCTestFactory.testAdapter("AzureBlobStorageAdapter",azureBlobStorageAdapter);
39+
40+
// Test Azure Blob Storage with direct access
41+
var azureBlobStorageDirectAccessAdapter = new AzureBlobStorageAdapter(process.env.AZURE_STORAGE_ACCOUNT_NAME, 'parseservertests', { storageAccessKey: process.env.AZURE_STORAGE_ACCOUNT_KEY, directAccess: true });
42+
43+
FCTestFactory.testAdapter("AzureBlobStorageAdapterDirect", azureBlobStorageDirectAccessAdapter);
44+
45+
} else if (!process.env.TRAVIS) {
46+
console.log("set AZURE_STORAGE_ACCOUNT_NAME and AZURE_STORAGE_ACCOUNT_KEY to test AzureBlobStorageAdapter")
47+
}
3348
});

spec/FilesControllerTestFactory.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ var Config = require("../src/Config");
55
var testAdapter = function(name, adapter) {
66
// Small additional tests to improve overall coverage
77

8-
var config = new Config(Parse.applicationId);
8+
var config = new Config(Parse.applicationId, 'testmount');
99
var filesController = new FilesController(adapter);
1010

1111
describe("FilesController with "+name,()=>{
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// AzureBlobStorageAdapter
2+
//
3+
// Stores Parse files in Azure Blob Storage.
4+
5+
import * as azure from 'azure-storage';
6+
import { FilesAdapter } from './FilesAdapter';
7+
8+
export class AzureBlobStorageAdapter extends FilesAdapter {
9+
// Creates an Azure Storage client.
10+
// Provide storage account name or storage account connection string as first parameter
11+
// Provide container name as second parameter
12+
// If you had provided storage account name, then also provide storage access key
13+
// Host is optional, Azure will default to the default host
14+
// directAccess defaults to false. If set to true, the file URL will be the actual blob URL
15+
constructor(
16+
storageAccountOrConnectionString,
17+
container, {
18+
storageAccessKey = '',
19+
host = '',
20+
directAccess = false
21+
} = {}
22+
) {
23+
super();
24+
25+
this._storageAccountOrConnectionString = storageAccountOrConnectionString;
26+
this._storageAccessKey = storageAccessKey;
27+
this._host = host;
28+
this._container = container;
29+
this._directAccess = directAccess;
30+
if (this._storageAccountOrConnectionString.indexOf(';') != -1) {
31+
// Connection string was passed
32+
// Extract storage account name
33+
// Storage account name is needed in getFileLocation
34+
this._storageAccountName = this._storageAccountOrConnectionString.substring(
35+
this._storageAccountOrConnectionString.indexOf('AccountName') + 12,
36+
this._storageAccountOrConnectionString.indexOf(';', this._storageAccountOrConnectionString.indexOf('AccountName') + 12)
37+
);
38+
} else {
39+
// Storage account name was passed
40+
this._storageAccountName = this._storageAccountOrConnectionString;
41+
}
42+
// Init client
43+
this._azureBlobStorageClient = azure.createBlobService(this._storageAccountOrConnectionString, this._storageAccessKey, this._host);
44+
}
45+
46+
// For a given config object, filename, and data, store a file in Azure Blob Storage
47+
// Returns a promise containing the Azure Blob Storage blob creation response
48+
createFile(config, filename, data) {
49+
let containerParams = {};
50+
if (this._directAccess) {
51+
containerParams.publicAccessLevel = 'blob';
52+
}
53+
54+
return new Promise((resolve, reject) => {
55+
this._azureBlobStorageClient.createContainerIfNotExists(
56+
this._container,
57+
containerParams,
58+
(cerror, cresult, cresponse) => {
59+
if (cerror) {
60+
return reject(cerror);
61+
}
62+
this._azureBlobStorageClient.createBlockBlobFromText(
63+
this._container,
64+
filename,
65+
data,
66+
(error, result, response) => {
67+
if (error) {
68+
return reject(error);
69+
}
70+
resolve(result);
71+
});
72+
});
73+
});
74+
}
75+
76+
deleteFile(config, filename) {
77+
return new Promise((resolve, reject) => {
78+
this._azureBlobStorageClient.deleteBlob(
79+
this._container,
80+
filename,
81+
(error, response) => {
82+
if (error) {
83+
return reject(error);
84+
}
85+
resolve(response);
86+
});
87+
});
88+
}
89+
90+
// Search for and return a file if found by filename
91+
// Returns a promise that succeeds with the buffer result from Azure Blob Storage
92+
getFileData(config, filename) {
93+
return new Promise((resolve, reject) => {
94+
this._azureBlobStorageClient.getBlobToText(
95+
this._container,
96+
filename,
97+
(error, text, blob, response) => {
98+
if (error) {
99+
return reject(error);
100+
}
101+
if(Buffer.isBuffer(text)) {
102+
resolve(text);
103+
}
104+
else {
105+
resolve(new Buffer(text, 'utf-8'));
106+
}
107+
108+
});
109+
});
110+
}
111+
112+
// Generates and returns the location of a file stored in Azure Blob Storage for the given request and filename
113+
// The location is the direct Azure Blob Storage link if the option is set, otherwise we serve the file through parse-server
114+
getFileLocation(config, filename) {
115+
if (this._directAccess) {
116+
return `http://${this._storageAccountName}.blob.core.windows.net/${this._container}/${filename}`;
117+
}
118+
return (config.mount + '/files/' + config.applicationId + '/' + encodeURIComponent(filename));
119+
}
120+
}
121+
122+
export default AzureBlobStorageAdapter;

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { PushRouter } from './Routers/PushRouter';
3939
import { randomString } from './cryptoUtils';
4040
import { RolesRouter } from './Routers/RolesRouter';
4141
import { S3Adapter } from './Adapters/Files/S3Adapter';
42+
import { AzureBlobStorageAdapter } from './Adapters/Files/AzureBlobStorageAdapter';
4243
import { SchemasRouter } from './Routers/SchemasRouter';
4344
import { SessionsRouter } from './Routers/SessionsRouter';
4445
import { setFeature } from './features';
@@ -258,4 +259,5 @@ function addParseCloud() {
258259
module.exports = {
259260
ParseServer: ParseServer,
260261
S3Adapter: S3Adapter,
262+
AzureBlobStorageAdapter: AzureBlobStorageAdapter
261263
};

0 commit comments

Comments
 (0)