feat: Support env vars configuration for WebSocket server (#14398)

This commit is contained in:
Ben Reinhart
2021-04-29 09:37:57 -07:00
committed by GitHub
parent 6541a03d0b
commit a2831382a9
4 changed files with 207 additions and 38 deletions

View File

@@ -64,6 +64,8 @@ npm install
Copy `config.example.json` to `config.json` and adjust the values for your environment.
Configuration via environment variables is also supported which can be helpful in certain contexts, e.g., deployment. `src/config.ts` can be consulted to see the full list of supported values.
## Superset Configuration
Configure the Superset Flask app to enable global async queries (in `superset_config.py`):

View File

@@ -0,0 +1,69 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { buildConfig } from '../src/config';
describe('buildConfig', () => {
test('builds configuration and applies env overrides', () => {
let config = buildConfig();
expect(config.jwtSecret).toEqual(
'test123-test123-test123-test123-test123-test123-test123',
);
expect(config.redis.host).toEqual('127.0.0.1');
expect(config.redis.port).toEqual(6379);
expect(config.redis.password).toEqual('');
expect(config.redis.db).toEqual(10);
expect(config.redis.ssl).toEqual(false);
expect(config.statsd.host).toEqual('127.0.0.1');
expect(config.statsd.port).toEqual(8125);
expect(config.statsd.globalTags).toEqual([]);
process.env.JWT_SECRET = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
process.env.REDIS_HOST = '10.10.10.10';
process.env.REDIS_PORT = '6380';
process.env.REDIS_PASSWORD = 'admin';
process.env.REDIS_DB = '4';
process.env.REDIS_SSL = 'true';
process.env.STATSD_HOST = '15.15.15.15';
process.env.STATSD_PORT = '8000';
process.env.STATSD_GLOBAL_TAGS = 'tag-1,tag-2';
config = buildConfig();
expect(config.jwtSecret).toEqual('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
expect(config.redis.host).toEqual('10.10.10.10');
expect(config.redis.port).toEqual(6380);
expect(config.redis.password).toEqual('admin');
expect(config.redis.db).toEqual(4);
expect(config.redis.ssl).toEqual(true);
expect(config.statsd.host).toEqual('15.15.15.15');
expect(config.statsd.port).toEqual(8000);
expect(config.statsd.globalTags).toEqual(['tag-1', 'tag-2']);
delete process.env.JWT_SECRET;
delete process.env.REDIS_HOST;
delete process.env.REDIS_PORT;
delete process.env.REDIS_PASSWORD;
delete process.env.REDIS_DB;
delete process.env.REDIS_SSL;
delete process.env.STATSD_HOST;
delete process.env.STATSD_PORT;
delete process.env.STATSD_GLOBAL_TAGS;
});
});

View File

@@ -0,0 +1,133 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
type ConfigType = {
port: number;
logLevel: string;
logToFile: boolean;
logFilename: string;
statsd: {
host: string;
port: number;
globalTags: Array<string>;
};
redis: {
port: number;
host: string;
password: string;
db: number;
ssl: boolean;
};
redisStreamPrefix: string;
redisStreamReadCount: number;
redisStreamReadBlockMs: number;
jwtSecret: string;
jwtCookieName: string;
socketResponseTimeoutMs: number;
pingSocketsIntervalMs: number;
gcChannelsIntervalMs: number;
};
function defaultConfig(): ConfigType {
return {
port: 8080,
logLevel: 'info',
logToFile: false,
logFilename: 'app.log',
redisStreamPrefix: 'async-events-',
redisStreamReadCount: 100,
redisStreamReadBlockMs: 5000,
jwtSecret: '',
jwtCookieName: 'async-token',
socketResponseTimeoutMs: 60 * 1000,
pingSocketsIntervalMs: 20 * 1000,
gcChannelsIntervalMs: 120 * 1000,
statsd: {
host: '127.0.0.1',
port: 8125,
globalTags: [],
},
redis: {
host: '127.0.0.1',
port: 6379,
password: '',
db: 0,
ssl: false,
},
};
}
function configFromFile(): Partial<ConfigType> {
const isTest = process.env.NODE_ENV === 'test';
const configFile = isTest ? '../config.test.json' : '../config.json';
try {
return require(configFile);
} catch (err) {
console.warn('config.json file not found');
return {};
}
}
const isPresent = (s: string) => /\S+/.test(s);
const toNumber = Number;
const toBoolean = (s: string) => s.toLowerCase() === 'true';
const toStringArray = (s: string) => s.split(',');
function applyEnvOverrides(config: ConfigType): ConfigType {
const envVarConfigSetter: { [envVar: string]: (val: string) => void } = {
PORT: val => (config.port = toNumber(val)),
LOG_LEVEL: val => (config.logLevel = val),
LOG_TO_FILE: val => (config.logToFile = toBoolean(val)),
LOG_FILENAME: val => (config.logFilename = val),
REDIS_STREAM_PREFIX: val => (config.redisStreamPrefix = val),
REDIS_STREAM_READ_COUNT: val =>
(config.redisStreamReadCount = toNumber(val)),
REDIS_STREAM_READ_BLOCK_MS: val =>
(config.redisStreamReadBlockMs = toNumber(val)),
JWT_SECRET: val => (config.jwtSecret = val),
JWT_COOKIE_NAME: val => (config.jwtCookieName = val),
SOCKET_RESPONSE_TIMEOUT_MS: val =>
(config.socketResponseTimeoutMs = toNumber(val)),
PING_SOCKETS_INTERVAL_MS: val =>
(config.pingSocketsIntervalMs = toNumber(val)),
GC_CHANNELS_INTERVAL_MS: val =>
(config.gcChannelsIntervalMs = toNumber(val)),
REDIS_HOST: val => (config.redis.host = val),
REDIS_PORT: val => (config.redis.port = toNumber(val)),
REDIS_PASSWORD: val => (config.redis.password = val),
REDIS_DB: val => (config.redis.db = toNumber(val)),
REDIS_SSL: val => (config.redis.ssl = toBoolean(val)),
STATSD_HOST: val => (config.statsd.host = val),
STATSD_PORT: val => (config.statsd.port = toNumber(val)),
STATSD_GLOBAL_TAGS: val => (config.statsd.globalTags = toStringArray(val)),
};
for (const [envVar, set] of Object.entries(envVarConfigSetter)) {
const envValue = process.env[envVar];
if (envValue && isPresent(envValue)) {
set(envValue);
}
}
return config;
}
export function buildConfig(): ConfigType {
const config = Object.assign(defaultConfig(), configFromFile());
return applyEnvOverrides(config);
}

View File

@@ -26,6 +26,7 @@ import Redis from 'ioredis';
import StatsD from 'hot-shots';
import { createLogger } from './logger';
import { buildConfig } from './config';
export type StreamResult = [
recordId: string,
@@ -79,45 +80,9 @@ interface ChannelValue {
const environment = process.env.NODE_ENV;
// default options
export const opts = {
port: 8080,
logLevel: 'info',
logToFile: false,
logFilename: 'app.log',
statsd: {
host: '127.0.0.1',
port: 8125,
globalTags: [],
},
redis: {
port: 6379,
host: '127.0.0.1',
password: '',
db: 0,
ssl: false,
},
redisStreamPrefix: 'async-events-',
redisStreamReadCount: 100,
redisStreamReadBlockMs: 5000,
jwtSecret: '',
jwtCookieName: 'async-token',
socketResponseTimeoutMs: 60 * 1000,
pingSocketsIntervalMs: 20 * 1000,
gcChannelsIntervalMs: 120 * 1000,
};
const startServer = process.argv[2] === 'start';
const configFile =
environment === 'test' ? '../config.test.json' : '../config.json';
let config = {};
try {
config = require(configFile);
} catch (err) {
console.error('config.json not found, using defaults');
}
// apply config overrides
Object.assign(opts, config);
export const opts = buildConfig();
// init logger
const logger = createLogger({