0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-10 23:36:14 -05:00

feature: storage adapter for images and themes (#7241)

refs #2852
- we offer the option to define a storage for themes and a storage for images
This commit is contained in:
Katharina Irrgang 2016-08-22 19:55:28 +02:00 committed by Hannah Wolfe
parent f174d4d56b
commit 41ae8c03b9
4 changed files with 163 additions and 28 deletions

View file

@ -96,7 +96,6 @@ ConfigManager.prototype.set = function (config) {
activeStorageAdapter, activeStorageAdapter,
activeSchedulingAdapter, activeSchedulingAdapter,
contentPath, contentPath,
storagePath,
schedulingPath, schedulingPath,
subdir, subdir,
assetHash; assetHash;
@ -162,11 +161,25 @@ ConfigManager.prototype.set = function (config) {
this._config.scheduling = this._config.scheduling || {}; this._config.scheduling = this._config.scheduling || {};
activeSchedulingAdapter = this._config.scheduling.active || defaultSchedulingAdapter; activeSchedulingAdapter = this._config.scheduling.active || defaultSchedulingAdapter;
// we support custom adapters located in content folder // storage.active can be an object like {images: 'my-custom-image-storage-adapter', themes: 'local-file-storage'}
if (activeStorageAdapter === defaultStorageAdapter) { // we ensure that passing a string to storage.active still works, but internal it's always an object
storagePath = path.join(corePath, '/server/storage/'); if (_.isString(activeStorageAdapter)) {
this._config.storage = _.merge(this._config.storage, {
active: {
images: activeStorageAdapter,
themes: defaultStorageAdapter
}
});
} else { } else {
storagePath = path.join(contentPath, 'storage'); // ensure there is a default image storage adapter
if (!this._config.storage.active.images) {
this._config.storage.active.images = defaultSchedulingAdapter;
}
// ensure there is a default theme storage adapter
if (!this._config.storage.active.themes) {
this._config.storage.active.themes = defaultSchedulingAdapter;
}
} }
if (activeSchedulingAdapter === defaultSchedulingAdapter) { if (activeSchedulingAdapter === defaultSchedulingAdapter) {
@ -184,7 +197,10 @@ ConfigManager.prototype.set = function (config) {
configExample: path.join(appRoot, 'config.example.js'), configExample: path.join(appRoot, 'config.example.js'),
corePath: corePath, corePath: corePath,
storage: path.join(storagePath, activeStorageAdapter), storagePath: {
default: path.join(corePath, '/server/storage/'),
custom: path.join(contentPath, 'storage/')
},
contentPath: contentPath, contentPath: contentPath,
themePath: path.resolve(contentPath, 'themes'), themePath: path.resolve(contentPath, 'themes'),
@ -205,9 +221,6 @@ ConfigManager.prototype.set = function (config) {
active: activeSchedulingAdapter, active: activeSchedulingAdapter,
path: schedulingPath path: schedulingPath
}, },
storage: {
active: activeStorageAdapter
},
theme: { theme: {
// normalise the URL by removing any trailing slash // normalise the URL by removing any trailing slash
url: this._config.url ? this._config.url.replace(/\/$/, '') : '' url: this._config.url ? this._config.url.replace(/\/$/, '') : ''

View file

@ -1,26 +1,39 @@
var errors = require('../errors'), var errors = require('../errors'),
config = require('../config'), config = require('../config'),
storage = {}; storage = {};
function getStorage(storageChoice) { /**
var storagePath, * type: images|themes
storageConfig; */
function getStorage(type) {
type = type || 'images';
storageChoice = config.storage.active; var storageChoice = config.storage.active[type],
storagePath = config.paths.storage; storageConfig = config.storage[storageChoice];
storageConfig = config.storage[storageChoice];
// CASE: type does not exist
if (!storageChoice) {
throw new errors.IncorrectUsage('No adapter found for type: ' + type);
}
// cache?
if (storage[storageChoice]) { if (storage[storageChoice]) {
return storage[storageChoice]; return storage[storageChoice];
} }
// CASE: load adapter from custom path (.../content/storage)
// CASE: load adapter from default path (.../server/storage)
try { try {
// TODO: determine if storage has all the necessary methods. storage[storageChoice] = require(config.paths.storagePath.custom + storageChoice);
storage[storageChoice] = require(storagePath); } catch (err1) {
} catch (e) { try {
errors.logError(e); storage[storageChoice] = require(config.paths.storagePath.default + storageChoice);
} catch (err2) {
throw err2;
}
} }
// TODO: determine if storage has all the necessary methods.
// Instantiate and cache the storage module instance. // Instantiate and cache the storage module instance.
storage[storageChoice] = new storage[storageChoice](storageConfig); storage[storageChoice] = new storage[storageChoice](storageConfig);

View file

@ -100,7 +100,7 @@ describe('Config', function () {
'subdir', 'subdir',
'config', 'config',
'configExample', 'configExample',
'storage', 'storagePath',
'contentPath', 'contentPath',
'corePath', 'corePath',
'themePath', 'themePath',
@ -175,13 +175,18 @@ describe('Config', function () {
describe('Storage', function () { describe('Storage', function () {
it('should default to local-file-store', function () { it('should default to local-file-store', function () {
var storagePath = path.join(config.paths.corePath, '/server/storage/', 'local-file-store'); config.paths.should.have.property('storagePath', {
default: path.join(config.paths.corePath, '/server/storage/'),
custom: path.join(config.paths.contentPath, 'storage/')
});
config.paths.should.have.property('storage', storagePath); config.storage.should.have.property('active', {
config.storage.should.have.property('active', 'local-file-store'); images: 'local-file-store',
themes: 'local-file-store'
});
}); });
it('should allow setting a custom active storage', function () { it('should allow setting a custom active storage as string', function () {
var storagePath = path.join(config.paths.contentPath, 'storage', 's3'); var storagePath = path.join(config.paths.contentPath, 'storage', 's3');
configUtils.set({ configUtils.set({
@ -191,10 +196,48 @@ describe('Config', function () {
} }
}); });
config.paths.should.have.property('storage', storagePath); config.storage.should.have.property('active', {
config.storage.should.have.property('active', 's3'); images: 's3',
themes: 'local-file-store'
});
config.storage.should.have.property('s3', {}); config.storage.should.have.property('s3', {});
}); });
it('should allow setting a custom active storage as object', function () {
var storagePath = path.join(config.paths.contentPath, 'storage', 's3');
configUtils.set({
storage: {
active: {
themes: 's3'
}
}
});
config.storage.should.have.property('active', {
images: 'local-file-store',
themes: 's3'
});
});
it('should allow setting a custom active storage as object', function () {
var storagePath = path.join(config.paths.contentPath, 'storage', 's3');
configUtils.set({
storage: {
active: {
images: 's2',
themes: 's3'
}
}
});
config.storage.should.have.property('active', {
images: 's2',
themes: 's3'
});
});
}); });
describe('Url', function () { describe('Url', function () {

View file

@ -0,0 +1,66 @@
var fs = require('fs-extra'),
should = require('should'),
configUtils = require('../../utils/configUtils'),
storage = require('../../../server/storage'),
errors = require('../../../server/errors'),
localFileStorage = require('../../../server/storage/local-file-store');
// to stop jshint complaining
should.equal(true, true);
describe('storage: index_spec', function () {
describe('default ghost storage config', function () {
it('load without a type', function () {
var chosenStorage = storage.getStorage();
(chosenStorage instanceof localFileStorage).should.eql(true);
});
it('load with themes type', function () {
var chosenStorage = storage.getStorage('themes');
(chosenStorage instanceof localFileStorage).should.eql(true);
});
it('load with unknown type', function () {
try {
storage.getStorage('theme');
} catch (err) {
(err instanceof errors.IncorrectUsage).should.eql(true);
}
});
});
describe('custom ghost storage config', function () {
it('images storage adapter is custom, themes is default', function () {
configUtils.set({
storage: {
active: {
images: 'custom-adapter'
}
}
});
var jsFile = '' +
'var util = require(\'util\');' +
'var StorageBase = require(__dirname + \'/../../core/server/storage/base\');' +
'var AnotherAdapter = function (){ StorageBase.call(this); };' +
'util.inherits(AnotherAdapter, StorageBase);' +
'AnotherAdapter.prototype.exists = function (){};' +
'AnotherAdapter.prototype.save = function (){};' +
'module.exports = AnotherAdapter', chosenStorage;
if (!fs.existsSync(configUtils.config.paths.storagePath.custom)) {
fs.mkdirSync(configUtils.config.paths.storagePath.custom);
}
fs.writeFileSync(configUtils.config.paths.storagePath.custom + 'custom-adapter.js', jsFile);
chosenStorage = storage.getStorage('themes');
(chosenStorage instanceof localFileStorage).should.eql(true);
chosenStorage = storage.getStorage('images');
(chosenStorage instanceof localFileStorage).should.eql(false);
fs.unlinkSync(configUtils.config.paths.storagePath.custom + 'custom-adapter.js');
});
});
});