Skip to content

Commit 11cadc7

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 98769a2 commit 11cadc7

File tree

5 files changed

+143
-2
lines changed

5 files changed

+143
-2
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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ var FilesController = require('../src/Controllers/FilesController').FilesControl
22
var GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter;
33
var S3Adapter = require("../src/Adapters/Files/S3Adapter").S3Adapter;
44
var GCSAdapter = require("../src/Adapters/Files/GCSAdapter").GCSAdapter;
5+
var AzureBlobStorageAdapter = require("../src/Adapters/Files/AzureBlobStorageAdapter").AzureBlobStorageAdapter;
56
var Config = require("../src/Config");
67

78
var FCTestFactory = require("./FilesControllerTestFactory");
@@ -49,4 +50,19 @@ describe("FilesController",()=>{
4950
} else if (!process.env.TRAVIS) {
5051
console.log("set GCP_PROJECT_ID, GCP_KEYFILE_PATH, and GCS_BUCKET to test GCSAdapter")
5152
}
53+
54+
if (process.env.AZURE_STORAGE_ACCOUNT_NAME && process.env.AZURE_STORAGE_ACCOUNT_KEY) {
55+
// Test the Azure Blob Storage Adapter
56+
var azureBlobStorageAdapter = new AzureBlobStorageAdapter(process.env.AZURE_STORAGE_ACCOUNT_NAME, 'parseservertests', { storageAccessKey: process.env.AZURE_STORAGE_ACCOUNT_KEY });
57+
58+
FCTestFactory.testAdapter("AzureBlobStorageAdapter",azureBlobStorageAdapter);
59+
60+
// Test Azure Blob Storage with direct access
61+
var azureBlobStorageDirectAccessAdapter = new AzureBlobStorageAdapter(process.env.AZURE_STORAGE_ACCOUNT_NAME, 'parseservertests', { storageAccessKey: process.env.AZURE_STORAGE_ACCOUNT_KEY, directAccess: true });
62+
63+
FCTestFactory.testAdapter("AzureBlobStorageAdapterDirect", azureBlobStorageDirectAccessAdapter);
64+
65+
} else if (!process.env.TRAVIS) {
66+
console.log("set AZURE_STORAGE_ACCOUNT_NAME and AZURE_STORAGE_ACCOUNT_KEY to test AzureBlobStorageAdapter")
67+
}
5268
});

spec/FilesControllerTestFactory.js

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

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

1010
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: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { PushRouter } from './Routers/PushRouter';
4040
import { randomString } from './cryptoUtils';
4141
import { RolesRouter } from './Routers/RolesRouter';
4242
import { S3Adapter } from './Adapters/Files/S3Adapter';
43+
import { AzureBlobStorageAdapter } from './Adapters/Files/AzureBlobStorageAdapter';
4344
import { SchemasRouter } from './Routers/SchemasRouter';
4445
import { SessionsRouter } from './Routers/SessionsRouter';
4546
import { setFeature } from './features';
@@ -260,5 +261,6 @@ function addParseCloud() {
260261
module.exports = {
261262
ParseServer: ParseServer,
262263
S3Adapter: S3Adapter,
263-
GCSAdapter: GCSAdapter
264+
GCSAdapter: GCSAdapter,
265+
AzureBlobStorageAdapter: AzureBlobStorageAdapter
264266
};

0 commit comments

Comments
 (0)