Support user defined configuration file

This commit is contained in:
Juan Cruz Viotti 2014-12-03 12:03:54 -04:00
parent 1b8290ade1
commit 511e753256
28 changed files with 211 additions and 63 deletions

View File

@ -21,6 +21,6 @@ exports.logout = ->
resin.auth.logout()
exports.signup = ->
signupUrl = resin.config.urls.signup
absUrl = url.resolve(resin.config.remoteUrl, signupUrl)
signupUrl = resin.settings.urls.signup
absUrl = url.resolve(resin.settings.remoteUrl, signupUrl)
open(absUrl)

View File

@ -3,7 +3,7 @@ resin = require('../resin')
helpers = require('../helpers/helpers')
exports.list = ->
resin.server.get resin.config.urls.keys, (error, response, keys) ->
resin.server.get resin.settings.urls.keys, (error, response, keys) ->
resin.errors.handle(error) if error?
resin.log.out resin.ui.widgets.table.horizontal keys, (key) ->
delete key.public_key
@ -16,18 +16,18 @@ exports.info = (id) ->
# TODO: We don't have a way to query a single ssh key yet.
# As a workaround, we request all of them, and filter
# the one we need. Fix once we have a better way.
resin.server.get resin.config.urls.keys, (error, response, keys) ->
resin.server.get resin.settings.urls.keys, (error, response, keys) ->
resin.errors.handle(error) if error?
key = _.findWhere(keys, { id })
if not key?
resin.errors.handle(new resin.errors.NotFound("key #{id}"))
key.public_key = '\n' + helpers.formatLongString(key.public_key, resin.config.sshKeyWidth)
key.public_key = '\n' + helpers.formatLongString(key.public_key, resin.settings.sshKeyWidth)
resin.log.out(resin.ui.widgets.table.vertical(key, _.identity, [ 'ID', 'Title', 'Public Key' ]))
exports.remove = (id) ->
confirmArgument = resin.cli.getArgument('yes')
resin.ui.patterns.remove 'key', confirmArgument, (callback) ->
url = _.template(resin.config.urls.sshKey, { id })
url = _.template(resin.settings.urls.sshKey, { id })
resin.server.delete(url, callback)
, resin.errors.handle

View File

@ -30,9 +30,9 @@ exports.logs = (uuid) ->
# PubNub needs to be initialised after logs
# action was called, otherwise it prevents
# all other actions from exiting on completion
pubnub = PubNub.init(resin.config.pubnub)
pubnub = PubNub.init(resin.settings.pubnub)
channel = _.template(resin.config.events.deviceLogs, { uuid })
channel = _.template(resin.settings.events.deviceLogs, { uuid })
pubnub.history
count: LOGS_HISTORY_COUNT

View File

@ -13,7 +13,7 @@ exports.download = (id) ->
wifiKey: resin.cli.getArgument('wifiKey')
fileName = resin.os.generateCacheName(id, params)
outputFile = resin.cli.getArgument('output') or path.join(resin.config.directories.os, fileName)
outputFile = resin.cli.getArgument('output') or path.join(resin.settings.directories.os, fileName)
async.waterfall [
@ -30,7 +30,7 @@ exports.download = (id) ->
parameters.appId = id
query = url.format(query: parameters)
downloadUrl = url.resolve(resin.config.urls.download, query)
downloadUrl = url.resolve(resin.settings.urls.download, query)
return callback(null, downloadUrl)

View File

@ -3,6 +3,6 @@ url = require('url')
resin = require('../resin')
exports.preferences = ->
preferencesUrl = resin.config.urls.preferences
absUrl = url.resolve(resin.config.remoteUrl, preferencesUrl)
preferencesUrl = resin.settings.urls.preferences
absUrl = url.resolve(resin.settings.remoteUrl, preferencesUrl)
open(absUrl)

View File

@ -132,7 +132,7 @@ resin.cli.addCommand
action: actions.os.download
permission: 'user'
resin.data.prefix.set resin.config.dataPrefix, (error) ->
resin.data.prefix.set resin.settings.dataPrefix, (error) ->
resin.errors.handle(error) if error?
resin.cli.parse(process.argv)

View File

@ -4,10 +4,10 @@ _ = require('lodash')
token = require('../token/token')
server = require('../server/server')
errors = require('../errors/errors')
config = require('../config')
settings = require('../settings')
exports.authenticate = (credentials, callback) ->
server.post config.urls.authenticate, credentials, (error, response) ->
server.post settings.urls.authenticate, credentials, (error, response) ->
return callback(error, response?.body)
exports.login = (credentials, callback) ->

View File

@ -4,7 +4,7 @@ _ = require('lodash')
async = require('async')
auth = require('./auth')
data = require('../data/data')
config = require('../config')
settings = require('../settings')
mock = require('../../../tests/utils/mock')
johnDoeFixture = require('../../../tests/fixtures/johndoe')
janeDoeFixture = require('../../../tests/fixtures/janedoe')
@ -19,7 +19,7 @@ describe 'Auth:', ->
beforeEach (done) ->
mock.fs.init()
data.prefix.set(config.dataPrefix, done)
data.prefix.set(settings.dataPrefix, done)
afterEach ->
mock.fs.restore()
@ -27,7 +27,7 @@ describe 'Auth:', ->
describe 'given valid credentials', ->
beforeEach ->
nock(config.remoteUrl)
nock(settings.remoteUrl)
.post('/login_', johnDoeFixture.credentials)
.reply(200, johnDoeFixture.token)
@ -68,7 +68,7 @@ describe 'Auth:', ->
describe 'given invalid credentials', ->
beforeEach ->
nock(config.remoteUrl)
nock(settings.remoteUrl)
.post('/login_')
.reply(401)
@ -93,11 +93,11 @@ describe 'Auth:', ->
describe 'given a logged in user', ->
beforeEach (done) ->
nock(config.remoteUrl)
nock(settings.remoteUrl)
.post('/login_', johnDoeFixture.credentials)
.reply(200, johnDoeFixture.token)
nock(config.remoteUrl)
nock(settings.remoteUrl)
.post('/login_', janeDoeFixture.credentials)
.reply(200, janeDoeFixture.token)

View File

@ -4,7 +4,7 @@ sinon = require('sinon')
expect = require('chai').expect
data = require('../data/data')
auth = require('../auth/auth')
config = require('../config')
settings = require('../settings')
cliPermissions = require('./cli-permissions')
johnDoeFixture = require('../../../tests/fixtures/johndoe')
mock = require('../../../tests/utils/mock')
@ -21,7 +21,7 @@ describe 'CLI Permissions:', ->
beforeEach (done) ->
mock.fs.init()
data.prefix.set(config.dataPrefix, done)
data.prefix.set(settings.dataPrefix, done)
afterEach ->
mock.fs.restore()
@ -59,7 +59,7 @@ describe 'CLI Permissions:', ->
describe 'if logged in', ->
beforeEach (done) ->
nock(config.remoteUrl)
nock(settings.remoteUrl)
.post('/login_', johnDoeFixture.credentials)
.reply(200, johnDoeFixture.token)

View File

@ -0,0 +1,17 @@
fs = require('fs')
errors = require('../errors/errors')
# User config loading should be sync, as we need to
# extend this module with the result before exporting
exports.loadUserConfig = (configFile) ->
return if not fs.existsSync(configFile)
if not fs.statSync(configFile).isFile()
throw new errors.InvalidConfigFile(configFile)
result = fs.readFileSync(configFile, encoding: 'utf8')
try
return JSON.parse(result)
catch error
throw new errors.InvalidConfigFile(configFile)

View File

@ -0,0 +1,51 @@
_ = require('lodash')
chai = require('chai')
expect = chai.expect
config = require('./config')
settings = require('../settings')
mock = require('../../../tests/utils/mock')
FILESYSTEM =
config:
name: 'config'
contents: JSON.stringify
directories:
plugins: 'myPlugins'
remoteUrl: 'http://localhost:9001'
directoryConfig:
name: 'directoryConfig'
contents: {}
notJSON:
name: 'notJSON'
contents: 'Not JSON content'
describe 'Config:', ->
beforeEach ->
mock.fs.init(FILESYSTEM)
afterEach ->
mock.fs.restore()
describe '#loadUserConfig()', ->
it 'should load the default config file', ->
configFile = FILESYSTEM.config.name
result = config.loadUserConfig(configFile)
expectedContents = JSON.parse(FILESYSTEM.config.contents)
expect(result).to.deep.equal(expectedContents)
it 'should return undefined if config file does not exist', ->
configFile = 'foobar'
result = config.loadUserConfig(configFile)
expect(result).to.be.undefined
it 'should throw an error if config file is not a file', ->
configFile = FILESYSTEM.directoryConfig.name
func = _.partial(config.loadUserConfig, configFile)
expect(func).to.throw(Error)
it 'should throw an error if config is not a json file', ->
configFile = FILESYSTEM.notJSON.name
func = _.partial(config.loadUserConfig, configFile)
expect(func).to.throw(Error)

View File

@ -5,12 +5,12 @@ fs = require('fs')
fsUtils = require('./fs-utils/fs-utils')
rimraf = require('rimraf')
dataPrefix = require('./data-prefix')
config = require('../config')
settings = require('../settings')
mock = require('../../../tests/utils/mock')
PREFIXES =
main: config.dataPrefix
new: "#{config.dataPrefix}-new"
main: settings.dataPrefix
new: "#{settings.dataPrefix}-new"
invalid: { path: '/abc' }
describe 'DataPrefix:', ->

View File

@ -3,7 +3,7 @@ _ = require('lodash')
fsUtils = require('./fs-utils/fs-utils')
mock = require('../../../tests/utils/mock')
async = require('async')
config = require('../config')
settings = require('../settings')
data = require('./data')
FILES_FIXTURES =
@ -16,15 +16,15 @@ FILES_FIXTURES =
FILESYSTEM =
text:
name: "#{config.dataPrefix}/text"
name: "#{settings.dataPrefix}/text"
contents: 'Hello World'
key: 'text'
directory:
name: "#{config.dataPrefix}/directory"
name: "#{settings.dataPrefix}/directory"
contents: {}
key: 'directory'
nested:
name: "#{config.dataPrefix}/nested/text"
name: "#{settings.dataPrefix}/nested/text"
contents: 'Nested Hello World'
key: 'nested/text'
@ -60,7 +60,7 @@ describe 'Data:', ->
beforeEach (done) ->
mock.fs.init(FILESYSTEM)
data.prefix.set(config.dataPrefix, done)
data.prefix.set(settings.dataPrefix, done)
afterEach ->
mock.fs.restore()

View File

@ -1,7 +1,7 @@
expect = require('chai').expect
mock = require('../../../../tests/utils/mock')
fsUtils = require('./fs-utils')
config = require('../../config')
settings = require('../../settings')
data = require('../../data/data')
FILESYSTEM =
@ -30,7 +30,7 @@ describe 'FsUtils:', ->
it 'should return true for valid paths', ->
for validPath in [
config.dataPrefix
settings.dataPrefix
'/Users/johndoe'
'../parent'
'./file/../file2'
@ -41,7 +41,7 @@ describe 'FsUtils:', ->
beforeEach (done) ->
mock.fs.init(FILESYSTEM)
data.prefix.set(config.dataPrefix, done)
data.prefix.set(settings.dataPrefix, done)
afterEach ->
mock.fs.restore()

View File

@ -7,6 +7,11 @@ exports.NotFound = class NotFound extends TypedError
@message = "Couldn't find #{name}"
@code = 1
exports.InvalidConfigFile = class NotFound extends TypedError
constructor: (file) ->
@message = "Invalid configuration file: #{file}"
@code = 1
exports.InvalidCredentials = class InvalidCredentials extends TypedError
constructor: ->
@message = 'Invalid credentials'

View File

@ -0,0 +1,16 @@
_ = require('lodash')
path = require('path')
exports.isAbsolutePath = (p) ->
return path.resolve(p) is p
exports.prefixObjectValuesWithPath = (prefix, object) ->
return _.object _.map object, (value, key) ->
result = [ key ]
if exports.isAbsolutePath(value)
result.push(value)
else
result.push(path.join(prefix, value))
return result

View File

@ -0,0 +1,48 @@
chai = require('chai')
chai.use(require('chai-string'))
expect = chai.expect
helpers = require('./helpers')
describe 'Helpers:', ->
describe '#isAbsolutePath()', ->
it 'should return true for absolute paths', ->
for path in [
'/'
'/Users/me'
'/usr/share'
]
expect(helpers.isAbsolutePath(path)).to.be.true
it 'should return false for relative paths', ->
for path in [
'./hello'
'../../resin'
'directory/'
]
expect(helpers.isAbsolutePath(path)).to.be.false
describe '#prefixObjectWithPath()', ->
it 'should add the path to every value', ->
prefix = '/resin'
object =
first: 'first'
second: './second'
result = helpers.prefixObjectValuesWithPath(prefix, object)
for key, value of result
expect(value).to.startsWith(prefix)
it 'should not add the prefix if the paths are absolute', ->
prefix = '/resin'
object =
first: '/first'
second: '/home/second'
third: '/usr/share/resin'
fourth: '/'
result = helpers.prefixObjectValuesWithPath(prefix, object)
expect(result).to.deep.equal(object)

View File

@ -10,4 +10,4 @@ module.exports =
ui: require('./ui')
cli: require('./cli/cli')
os: require('./os/os')
config: require('./config')
settings: require('./settings')

View File

@ -1,7 +1,7 @@
_ = require('lodash')
Canvas = require('resin-platform-api')
Promise = require('bluebird')
config = require('../config')
settings = require('../settings')
server = require('../server/server')
promisifiedServerRequest = Promise.promisify(server.request, server)
@ -15,6 +15,6 @@ class CanvasRequestService extends Canvas(_, Promise)
throw new Error(body)
module.exports = new CanvasRequestService
url: config.remoteUrl
apiPrefix: config.apiPrefix
url: settings.remoteUrl
apiPrefix: settings.apiPrefix
withCredentials: true

View File

@ -9,10 +9,10 @@ chai.use(chaiAsPromised)
data = require('../data/data')
mock = require('../../../tests/utils/mock')
canvas = require('./_canvas')
config = require('../config')
settings = require('../settings')
URI =
application: url.resolve(config.apiPrefix, 'application')
application: url.resolve(settings.apiPrefix, 'application')
RESPONSE =
applications:
@ -25,7 +25,7 @@ describe 'Canvas:', ->
beforeEach (done) ->
mock.fs.init()
data.prefix.set(config.dataPrefix, done)
data.prefix.set(settings.dataPrefix, done)
afterEach ->
mock.fs.restore()
@ -37,7 +37,7 @@ describe 'Canvas:', ->
mock.connection.restore()
beforeEach ->
nock(config.remoteUrl)
nock(settings.remoteUrl)
.get(URI.application)
.reply(200, RESPONSE.applications)

View File

@ -2,7 +2,7 @@ _ = require('lodash')
canvas = require('./_canvas')
errors = require('../errors/errors')
server = require('../server/server')
config = require('../config')
settings = require('../settings')
exports.getAll = (callback) ->
return canvas.get
@ -61,5 +61,5 @@ exports.remove = (id, callback) ->
return callback(error)
exports.restart = (id, callback) ->
url = _.template(config.urls.applicationRestart, { id })
url = _.template(settings.urls.applicationRestart, { id })
server.post(url, callback)

View File

@ -2,7 +2,7 @@ canvas = require('./_canvas')
_ = require('lodash')
errors = require('../errors/errors')
server = require('../server/server')
config = require('../config')
settings = require('../settings')
exports.getAll = (callback) ->
return canvas.get
@ -46,4 +46,4 @@ exports.remove = (id, callback) ->
return callback(error)
exports.identify = (uuid, callback) ->
server.post(config.urls.identify, { uuid }, callback)
server.post(settings.urls.identify, { uuid }, callback)

View File

@ -4,7 +4,7 @@ progress = require('request-progress')
urlResolve = require('url').resolve
async = require('async')
connection = require('../../connection/connection')
config = require('../config')
settings = require('../settings')
token = require('../token/token')
exports.request = (options = {}, outerCallback, onProgress) ->
@ -26,7 +26,7 @@ exports.request = (options = {}, outerCallback, onProgress) ->
token.getToken(callback)
(savedToken, callback) ->
options.url = urlResolve(config.remoteUrl, options.url)
options.url = urlResolve(settings.remoteUrl, options.url)
if options.method?
options.method = options.method.toUpperCase()

View File

@ -4,13 +4,13 @@ nock = require('nock')
url = require('url')
sinon = require('sinon')
server = require('./server')
config = require('../config')
settings = require('../settings')
token = require('../token/token')
data = require('../data/data')
mock = require('../../../tests/utils/mock')
johnDoeFixture = require('../../../tests/fixtures/johndoe.json')
TEST_URI = config.remoteUrl
TEST_URI = settings.remoteUrl
URI =
ok: '/ok'
@ -50,7 +50,7 @@ describe 'Server:', ->
nock(TEST_URI)[lowercaseMethod](URI.ok).reply(200, status: STATUS.ok)
mock.fs.init()
data.prefix.set(config.dataPrefix, done)
data.prefix.set(settings.dataPrefix, done)
afterEach ->
mock.fs.restore()
@ -140,7 +140,7 @@ describe 'Server:', ->
it 'should accept a full url', (done) ->
server.request {
method: 'GET'
url: url.resolve(config.remoteUrl, URI.ok)
url: url.resolve(settings.remoteUrl, URI.ok)
}, (error, response) ->
expect(error).to.not.exist
expect(response.body.status).to.equal(STATUS.ok)

View File

@ -1,10 +1,12 @@
_ = require('lodash')
path = require('path')
fs = require('fs')
userHome = require('user-home')
helpers = require('./helpers/helpers')
errors = require('./errors/errors')
config = require('./config/config')
config =
# TODO: Should be configurable
settings =
remoteUrl: 'https://staging.resin.io'
apiPrefix: '/ewa/'
@ -16,6 +18,11 @@ config =
plugins: 'plugins'
os: 'os'
files:
# TODO: Accept an option that overrides this
config: 'config'
pubnub:
subscribe_key: 'sub-c-bbc12eba-ce4a-11e3-9782-02ee2ddab7fe'
publish_key: 'pub-c-6cbce8db-bfd1-4fdf-a8c8-53671ae2b226'
@ -34,7 +41,10 @@ config =
sshKey: '/user/keys/<%= id %>'
download: '/download'
config.directories = _.object _.map config.directories, (value, key) ->
return [ key, path.join(config.dataPrefix, value) ]
settings.directories = helpers.prefixObjectValuesWithPath(settings.dataPrefix, settings.directories)
settings.files = helpers.prefixObjectValuesWithPath(settings.dataPrefix, settings.files)
module.exports = config
# Attempt to load user configuration
_.extend(settings, config.loadUserConfig(settings.files.config) or {})
module.exports = settings

View File

@ -1,7 +1,7 @@
expect = require('chai').expect
async = require('async')
token = require('./token')
config = require('../config')
settings = require('../settings')
data = require('../data/data')
mock = require('../../../tests/utils/mock')
@ -12,7 +12,7 @@ describe 'Token:', ->
beforeEach (done) ->
mock.fs.init()
data.prefix.set(config.dataPrefix, done)
data.prefix.set(settings.dataPrefix, done)
afterEach ->
mock.fs.restore()

View File

@ -32,7 +32,8 @@
"sinon-chai": "~2.6.0",
"gulp-coffee": "~2.2.0",
"gulp-util": "~3.0.1",
"run-sequence": "~1.0.2"
"run-sequence": "~1.0.2",
"chai-string": "~1.1.0"
},
"dependencies": {
"request": "~2.47.0",

View File

@ -10,7 +10,7 @@ exports.fs =
# Mock data prefix automatically to remove
# duplication in most of the tests
mockFsOptions[resin.config.dataPrefix] = mockFs.directory()
mockFsOptions[resin.settings.dataPrefix] = mockFs.directory()
for key, value of filesystemConfig
mockFsOptions[value.name] = value.contents