-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Optionally persist [Github] tokens in Redis (#1939)
This is a fairly simple addition of a Redis-backed TokenPersistence. When GithubConstellation is initialized, it will create a FsTokenPersistence or a RedisTokenPersistence based on configuration. Have added tests of the Redis backend as an integration test, and ensured the server starts up correctly when a `REDIS_URL` is configured. Ref: #1848
- Loading branch information
1 parent
edb7d82
commit a16d436
Showing
13 changed files
with
281 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
'use strict' | ||
|
||
const fsos = require('fsos') | ||
const githubAuth = require('./github-auth') | ||
const TokenPersistence = require('./token-persistence') | ||
|
||
class FsTokenPersistence extends TokenPersistence { | ||
constructor({ path }) { | ||
super() | ||
this.path = path | ||
} | ||
|
||
async initialize() { | ||
let contents | ||
try { | ||
contents = await fsos.get(this.path) | ||
} catch (e) { | ||
if (e.code === 'ENOENT') { | ||
contents = '[]' | ||
} else { | ||
throw e | ||
} | ||
} | ||
|
||
const tokens = JSON.parse(contents) | ||
tokens.forEach(tokenString => { | ||
githubAuth.addGithubToken(tokenString) | ||
}) | ||
} | ||
|
||
async save() { | ||
const tokens = githubAuth.getAllTokenIds() | ||
await fsos.set(this.path, JSON.stringify(tokens)) | ||
} | ||
|
||
async onTokenAdded(token) { | ||
await this.save() | ||
} | ||
|
||
async onTokenRemoved(token) { | ||
await this.save() | ||
} | ||
} | ||
|
||
module.exports = FsTokenPersistence |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
'use strict' | ||
|
||
const { promisify } = require('util') | ||
const RedisServer = require('redis-server') | ||
const redis = require('redis') | ||
const { expect } = require('chai') | ||
const RedisTokenPersistence = require('./redis-token-persistence') | ||
const githubAuth = require('./github-auth') | ||
|
||
describe('Redis token persistence', function() { | ||
beforeEach(githubAuth.removeAllTokens) | ||
afterEach(githubAuth.removeAllTokens) | ||
|
||
let server | ||
// In CI, expect redis already to be running. | ||
if (!process.env.CI) { | ||
beforeEach(async function() { | ||
server = new RedisServer({ config: { host: 'localhost' } }) | ||
await server.open() | ||
}) | ||
} | ||
|
||
const key = 'tokenPersistenceIntegrationTest' | ||
|
||
let client | ||
beforeEach(async function() { | ||
client = redis.createClient() | ||
const del = promisify(client.del).bind(client) | ||
await del(key) | ||
}) | ||
afterEach(async function() { | ||
if (client) { | ||
const quit = promisify(client.quit).bind(client) | ||
await quit() | ||
client = undefined | ||
} | ||
}) | ||
|
||
if (!process.env.CI) { | ||
afterEach(async function() { | ||
await server.close() | ||
server = undefined | ||
}) | ||
} | ||
|
||
let persistence | ||
beforeEach(function() { | ||
persistence = new RedisTokenPersistence({ key }) | ||
}) | ||
afterEach(async function() { | ||
if (persistence) { | ||
await persistence.stop() | ||
persistence = undefined | ||
} | ||
}) | ||
|
||
context('when the key does not exist', function() { | ||
it('does nothing', async function() { | ||
await persistence.initialize() | ||
expect(githubAuth.getAllTokenIds()).to.deep.equal([]) | ||
}) | ||
}) | ||
|
||
context('when the key exists', function() { | ||
const initialTokens = ['a', 'b', 'c'].map(char => char.repeat(40)) | ||
|
||
beforeEach(async function() { | ||
const rpush = promisify(client.rpush).bind(client) | ||
await rpush(key, initialTokens) | ||
}) | ||
|
||
let lrange | ||
beforeEach(function() { | ||
lrange = promisify(client.lrange).bind(client) | ||
}) | ||
|
||
it('loads the contents', async function() { | ||
await persistence.initialize() | ||
expect(githubAuth.getAllTokenIds()).to.deep.equal(initialTokens) | ||
}) | ||
|
||
context('when tokens are added', function() { | ||
it('saves the change', async function() { | ||
const newToken = 'e'.repeat(40) | ||
const expected = initialTokens.slice() | ||
expected.push(newToken) | ||
|
||
await persistence.initialize() | ||
githubAuth.addGithubToken(newToken) | ||
await persistence.noteTokenAdded(newToken) | ||
|
||
const savedTokens = await lrange(key, 0, -1) | ||
expect(savedTokens).to.deep.equal(expected) | ||
}) | ||
}) | ||
|
||
context('when tokens are removed', function() { | ||
it('saves the change', async function() { | ||
const expected = Array.from(initialTokens) | ||
const toRemove = expected.pop() | ||
|
||
await persistence.initialize() | ||
|
||
githubAuth.rmGithubToken(toRemove) | ||
await persistence.noteTokenRemoved(toRemove) | ||
|
||
const savedTokens = await lrange(key, 0, -1) | ||
expect(savedTokens).to.deep.equal(expected) | ||
}) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
'use strict' | ||
|
||
const redis = require('redis') | ||
const { promisify } = require('util') | ||
const log = require('./log') | ||
const githubAuth = require('./github-auth') | ||
const TokenPersistence = require('./token-persistence') | ||
|
||
class RedisTokenPersistence extends TokenPersistence { | ||
constructor({ url, key }) { | ||
super() | ||
this.url = url | ||
this.key = key | ||
} | ||
|
||
async initialize() { | ||
this.client = redis.createClient(this.url) | ||
this.client.on('error', e => { | ||
log.error(e) | ||
}) | ||
|
||
const lrange = promisify(this.client.lrange).bind(this.client) | ||
|
||
let tokens | ||
try { | ||
tokens = await lrange(this.key, 0, -1) | ||
} catch (e) { | ||
log.error(e) | ||
} | ||
|
||
tokens.forEach(tokenString => { | ||
githubAuth.addGithubToken(tokenString) | ||
}) | ||
} | ||
|
||
async stop() { | ||
const quit = promisify(this.client.quit).bind(this.client) | ||
await quit() | ||
} | ||
|
||
async onTokenAdded(token) { | ||
const rpush = promisify(this.client.rpush).bind(this.client) | ||
await rpush(this.key, token) | ||
} | ||
|
||
async onTokenRemoved(token) { | ||
const lrem = promisify(this.client.lrem).bind(this.client) | ||
await lrem(this.key, 0, token) | ||
} | ||
} | ||
|
||
module.exports = RedisTokenPersistence |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.