UNPKG

32.2 kBJavaScriptView Raw
1"use strict";
2/*
3 * Copyright © 2018 Atomist, Inc.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
19 function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
20 return new (P || (P = Promise))(function (resolve, reject) {
21 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
22 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
23 function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
24 step((generator = generator.apply(thisArg, _arguments || [])).next());
25 });
26};
27Object.defineProperty(exports, "__esModule", { value: true });
28/* tslint:disable:max-file-line-count */
29const appRoot = require("app-root-path");
30const cluster = require("cluster");
31const fg = require("fast-glob");
32const fs = require("fs-extra");
33const stringify = require("json-stringify-safe");
34const _ = require("lodash");
35const os = require("os");
36const p = require("path");
37const semver = require("semver");
38const globals_1 = require("./globals");
39const WebSocketLifecycle_1 = require("./internal/transport/websocket/WebSocketLifecycle");
40const config_1 = require("./internal/util/config");
41const shutdown_1 = require("./internal/util/shutdown");
42const string_1 = require("./internal/util/string");
43const GraphClientFactory_1 = require("./spi/graph/GraphClientFactory");
44const httpClient_1 = require("./spi/http/httpClient");
45const wsClient_1 = require("./spi/http/wsClient");
46const statsdClient_1 = require("./spi/statsd/statsdClient");
47const logger_1 = require("./util/logger");
48const packageJson_1 = require("./util/packageJson");
49/**
50 * Generate defaults for various configuration option values. These
51 * will only be used if values are not provided by any source. Values
52 * not provided here will be `undefined`.
53 *
54 * @return default configuration
55 */
56function defaultConfiguration() {
57 const pj = packageJson_1.loadHostPackageJson() || {};
58 pj.name = pj.name || "atm-client-" + string_1.guid();
59 pj.version = pj.version || "0.0.0";
60 pj.keywords = pj.keywords || [];
61 const cfg = loadDefaultConfiguration();
62 cfg.name = pj.name;
63 cfg.version = pj.version;
64 cfg.keywords = pj.keywords;
65 cfg.application = pj.name.replace(/^@.*?\//, "");
66 return cfg;
67}
68exports.defaultConfiguration = defaultConfiguration;
69/**
70 * Exposes the configuration for lookup of configuration values.
71 * This is useful for components to obtain values eg. from configuration.custom
72 * like user provided secrets etc.
73 * @param {string} path the property path evaluated against the configuration instance
74 * @returns {T}
75 */
76function configurationValue(path = "", defaultValue) {
77 if (globals_1.automationClientInstance()) {
78 const conf = globals_1.automationClientInstance().configuration;
79 let value;
80 if (!path || path.length === 0) {
81 value = conf;
82 }
83 else {
84 value = _.get(conf, path);
85 }
86 // tslint:disable-next-line:no-null-keyword
87 if (value !== null && value !== undefined) {
88 return value;
89 }
90 }
91 if (defaultValue !== undefined) {
92 return defaultValue;
93 }
94 throw new Error(`Required @Value '${path}' not available`);
95}
96exports.configurationValue = configurationValue;
97/**
98 * Return the default configuration based on NODE_ENV or ATOMIST_ENV.
99 * ATOMIST_ENV takes precedence if it is set.
100 */
101function loadDefaultConfiguration() {
102 const cfg = exports.LocalDefaultConfiguration;
103 // Insert default factories
104 _.set(cfg, "http.client.factory", httpClient_1.defaultHttpClientFactory());
105 _.set(cfg, "ws.client.factory", wsClient_1.defaultWebSocketFactory());
106 _.set(cfg, "graphql.client.factory", GraphClientFactory_1.defaultGraphClientFactory());
107 _.set(cfg, "statsd.client.factory", statsdClient_1.defaultStatsDClientFactory());
108 let envSpecificCfg = {};
109 const nodeEnv = process.env.ATOMIST_ENV || process.env.NODE_ENV;
110 if (nodeEnv === "production") {
111 envSpecificCfg = exports.ProductionDefaultConfiguration;
112 cfgLog("production default");
113 }
114 else if (nodeEnv === "staging" || nodeEnv === "testing") {
115 envSpecificCfg = exports.TestingDefaultConfiguration;
116 cfgLog("testing default");
117 }
118 else if (nodeEnv) {
119 cfg.environment = nodeEnv;
120 }
121 // For internal testing use to switch to staging apis
122 if (process.env.ATOMIST_ENDPOINTS === "staging") {
123 cfg.endpoints = {
124 graphql: "https://automation-staging.atomist.services/graphql/team",
125 api: "https://automation-staging.atomist.services/registration",
126 auth: "https://api-staging.atomist.services/v2/auth",
127 };
128 }
129 return mergeConfigs(cfg, envSpecificCfg);
130}
131/**
132 * Return Atomist user configuration directory.
133 */
134function userConfigDir() {
135 return p.join(os.homedir(), ".atomist");
136}
137/**
138 * Return user automation client configuration path.
139 */
140function userConfigPath() {
141 const clientConfigFile = "client.config.json";
142 return p.join(userConfigDir(), clientConfigFile);
143}
144exports.userConfigPath = userConfigPath;
145/**
146 * Return user automation client configuration paths including
147 * such referenced by configuration profiles.
148 */
149function userConfigPaths() {
150 const configPaths = [userConfigPath()];
151 const profiles = process.env.ATOMIST_CONFIG_PROFILE || process.env.ATOMIST_CONFIG_PROFILES;
152 if (!!profiles) {
153 for (const profile of profiles.split(",")) {
154 configPaths.push(p.join(userConfigDir(), `client.config-${profile}.json`));
155 }
156 }
157 return configPaths;
158}
159exports.userConfigPaths = userConfigPaths;
160/**
161 * Write user config securely, creating directories as necessary.
162 */
163function writeUserConfig(cfg) {
164 const cfgDir = userConfigDir();
165 return fs.ensureDir(cfgDir)
166 .then(() => fs.chmod(cfgDir, 0o700))
167 .then(() => fs.writeJson(userConfigPath(), cfg, {
168 spaces: 2,
169 encoding: "utf8",
170 mode: 0o600,
171 }));
172}
173exports.writeUserConfig = writeUserConfig;
174/**
175 * Read and return user config from UserConfigFile.
176 */
177function getUserConfig() {
178 const configPaths = userConfigPaths();
179 const userConfigs = [];
180 for (const configPath of configPaths) {
181 if (fs.existsSync(configPath)) {
182 try {
183 const cfg = fs.readJsonSync(configPath);
184 // user config should not have name or version
185 if (cfg.name) {
186 delete cfg.name;
187 }
188 if (cfg.version) {
189 delete cfg.version;
190 }
191 userConfigs.push(cfg);
192 }
193 catch (e) {
194 e.message = `Failed to read user config: ${e.message}`;
195 throw e;
196 }
197 }
198 }
199 if (userConfigs.length > 0) {
200 return mergeConfigs({}, ...userConfigs);
201 }
202 else {
203 return undefined;
204 }
205}
206exports.getUserConfig = getUserConfig;
207/**
208 * Log the loading of a configuration
209 *
210 * @param source name of configuration source
211 */
212function cfgLog(source) {
213 if (cluster.isMaster) {
214 logger_1.logger.debug(`Loading configuration '${source}'`);
215 }
216}
217/**
218 * Overwrite values in the former configuration with values in the
219 * latter. The start object is modified.
220 *
221 * @param obj starting configuration
222 * @param override configuration values to add/override those in start
223 * @return resulting merged configuration
224 */
225function mergeConfigs(obj, ...sources) {
226 return _.mergeWith(obj, ...sources, (objValue, srcValue) => {
227 if (_.isArray(srcValue)) {
228 return srcValue;
229 }
230 });
231}
232exports.mergeConfigs = mergeConfigs;
233/**
234 * Overwrite values in the former configuration with values in the
235 * latter. The start object is modified. Arrays are concatenated.
236 *
237 * @param obj starting configuration
238 * @param override configuration values to add/override those in start
239 * @return resulting merged configuration
240 */
241function deepMergeConfigs(obj, ...sources) {
242 return _.mergeWith(obj, ...sources, (objValue, srcValue) => {
243 if (_.isArray(objValue) && srcValue) {
244 return objValue.concat(srcValue);
245 }
246 });
247}
248exports.deepMergeConfigs = deepMergeConfigs;
249/**
250 * Merge a user's global and proper per-module configuration, if it
251 * exists. Values from the per-module configuration take precedence
252 * over the user-wide values. Per-module configuration is gotten from
253 * the first per-module configuration that matches name and,
254 * optionally, the version is within the per-module configuration's
255 * version range. A module configuration without a version range
256 * matches the named module with any version. If no version is
257 * provided, any version range is satisfied, meaning the first
258 * per-module configuration with a matching name is used. If no name
259 * is provide, only the user configuration is loaded. The first
260 * per-module match is used. This means if you have multiple
261 * configurations for the same named module and you want to include a
262 * default configuration for that module, put a configuration without
263 * a version range _after_ all the configurations with version ranges.
264 * Note that only values from the first per-module match are used.
265 *
266 * @param userConfig the user's configuration, which may include per-module configuration
267 * @param name automation client package name to load as module config if it exists
268 * @param version automation client package version to load as module config if
269 * version satifies module config version range
270 * @return the merged module and user configuration
271 */
272function resolveModuleConfig(userConfig, name, version) {
273 const cfg = {};
274 if (userConfig) {
275 cfgLog(userConfigPath());
276 const uc = _.cloneDeep(userConfig);
277 let mc = {};
278 if (userConfig.modules) {
279 delete uc.modules;
280 if (name) {
281 let modCfg;
282 const moduleConfigs = userConfig.modules.filter(m => m.name === name);
283 if (version) {
284 modCfg = moduleConfigs.find(m => !m.version || semver.satisfies(version, m.version));
285 }
286 else if (moduleConfigs.length > 0) {
287 modCfg = moduleConfigs[0];
288 }
289 if (modCfg) {
290 cfgLog("module");
291 if (modCfg.name) {
292 delete modCfg.name;
293 }
294 if (modCfg.version) {
295 delete modCfg.version;
296 }
297 mc = modCfg;
298 }
299 }
300 }
301 mergeConfigs(cfg, uc, mc);
302 }
303 return cfg;
304}
305exports.resolveModuleConfig = resolveModuleConfig;
306/**
307 * Try to read user config, overriding its values with a per-module
308 * configuration that matches this automation.
309 *
310 * @param name automation client package name to load as module config if it exists
311 * @param version automation client package version to load as module config if
312 * version satifies module config version range
313 * @return module-specific config with user config supplying defaults
314 */
315function loadUserConfiguration(name, version) {
316 const userConfig = getUserConfig();
317 return resolveModuleConfig(userConfig, name, version);
318}
319exports.loadUserConfiguration = loadUserConfiguration;
320/**
321 * Load the automation configuration from the configuration object
322 * exported from cfgPath and return it. If no configuration path is
323 * provided, the package will be searched for a file named
324 * atomist.config.js. If no atomist.config.js is found, an empty
325 * object is returned. If more than one is found, an exception is
326 * thrown.
327 *
328 * @param cfgPath location of automation configuration
329 * @return automation configuration or undefined
330 */
331function loadAutomationConfig(configPath) {
332 return __awaiter(this, void 0, void 0, function* () {
333 let cfgPath = configPath;
334 if (!cfgPath) {
335 const cfgFile = "atomist.config.js";
336 const files = yield fg(`${appRoot.path}/**/${cfgFile}`, { ignore: [`${appRoot.path}/**/{.git,node_modules}/**`] });
337 if (files.length === 1) {
338 cfgPath = files[0];
339 }
340 else if (files.length > 1) {
341 throw new Error(`More than one automation configuration found in package: ${files.join(", ")}`);
342 }
343 }
344 if (cfgPath) {
345 try {
346 let cfg = require(cfgPath).configuration;
347 if (cfg instanceof Promise) {
348 cfg = yield cfg;
349 }
350 cfgLog(cfgPath);
351 return cfg;
352 }
353 catch (e) {
354 e.message = `Failed to load ${cfgPath}.configuration: ${e.message}`;
355 throw e;
356 }
357 }
358 return undefined;
359 });
360}
361exports.loadAutomationConfig = loadAutomationConfig;
362/**
363 * Load the automation configuration from the configuration objects
364 * exported and merged by all index.js files in the automation client.
365 *
366 * @return automation configuration
367 */
368function loadIndexConfig() {
369 return __awaiter(this, void 0, void 0, function* () {
370 const cfgFile = "index.js";
371 const files = yield fg(`${appRoot.path}/**/${cfgFile}`, { ignore: [`${appRoot.path}/**/{.git,node_modules}/**`] });
372 if (files.length > 0) {
373 const cfgs = [];
374 for (const f of files) {
375 try {
376 let cfg = require(f).configuration || {};
377 if (cfg instanceof Promise) {
378 cfg = yield cfg;
379 }
380 cfgLog(f);
381 cfgs.push(cfg);
382 }
383 catch (e) {
384 e.message = `Failed to load ${f}.configuration: ${e.message}`;
385 throw e;
386 }
387 }
388 return deepMergeConfigs({}, ...cfgs);
389 }
390 return {};
391 });
392}
393exports.loadIndexConfig = loadIndexConfig;
394/**
395 * Load configuration from the file defined by the ATOMIST_CONFIG_PATH
396 * environment variable, if it the variable is defined and the file
397 * exists, and return it. The contents of the ATOMIST_CONFIG_PATH
398 * file should be serialized JSON of AutomationServerOptions. If the
399 * environment variable is not defined or the file path specified by
400 * its value cannot be read as JSON, an empty object is returned.
401 *
402 * @return automation server options
403 */
404function loadAtomistConfigPath() {
405 let cfg = {};
406 if (process.env.ATOMIST_CONFIG_PATH) {
407 try {
408 cfg = fs.readJsonSync(process.env.ATOMIST_CONFIG_PATH);
409 cfgLog("ATOMIST_CONFIG_PATH");
410 }
411 catch (e) {
412 e.message = `Failed to read ATOMIST_CONFIG_PATH: ${e.message}`;
413 throw e;
414 }
415 }
416 return cfg;
417}
418exports.loadAtomistConfigPath = loadAtomistConfigPath;
419/**
420 * Load configuration from the ATOMIST_CONFIG environment variable, if
421 * it the variable is defined, and merge it into the passed in
422 * configuration. The value of the ATOMIST_CONFIG environment
423 * variable should be serialized JSON of AutomationServerOptions. The
424 * values from the environment variable will override values in the
425 * passed in configuration. If the environment variable is not
426 * defined, the passed in configuration is returned unchanged.
427 *
428 * @return automation server options
429 */
430function loadAtomistConfig() {
431 let cfg = {};
432 if (process.env.ATOMIST_CONFIG) {
433 try {
434 cfg = JSON.parse(process.env.ATOMIST_CONFIG);
435 cfgLog("ATOMIST_CONFIG");
436 }
437 catch (e) {
438 e.message = `Failed to parse contents of ATOMIST_CONFIG environment variable: ${e.message}`;
439 throw e;
440 }
441 }
442 return cfg;
443}
444exports.loadAtomistConfig = loadAtomistConfig;
445/**
446 * Examine environment, config, and cfg for Atomist workspace IDs.
447 * The ATOMIST_WORKSPACES environment variable takes precedence over
448 * the config "workspaceIds", which takes precedence over
449 * cfg.workspaceId, which may be undefined, null, or an empty array.
450 *
451 * @param cfg current configuration, whose workspaceIds
452 * properties may be modified by this function
453 * @return the resolved workspace IDs
454 */
455function resolveWorkspaceIds(cfg) {
456 if (process.env.ATOMIST_WORKSPACES) {
457 cfg.workspaceIds = process.env.ATOMIST_WORKSPACES.split(",");
458 }
459 else if (config_1.config("workspaceIds")) {
460 cfg.workspaceIds = config_1.config("workspaceIds");
461 }
462 return cfg.workspaceIds;
463}
464exports.resolveWorkspaceIds = resolveWorkspaceIds;
465/**
466 * Resolve the HTTP port from the environment and configuration. The
467 * PORT environment variable takes precedence over the config value.
468 */
469function resolvePort(cfg) {
470 if (process.env.PORT) {
471 cfg.http.port = parseInt(process.env.PORT, 10);
472 }
473 return cfg.http.port;
474}
475exports.resolvePort = resolvePort;
476const EnvironmentVariablePrefix = "ATOMIST_";
477/**
478 * Resolve ATOMIST_ environment variables and add them to config.
479 * Variables of like ATOMIST_custom_foo_bar will be converted to
480 * a json path of custom.foo.bar.
481 * @param {Configuration} cfg
482 */
483function resolveEnvironmentVariables(cfg) {
484 for (const key in process.env) {
485 if (key.startsWith(EnvironmentVariablePrefix) && process.env.hasOwnProperty(key)) {
486 const cleanKey = key.slice(EnvironmentVariablePrefix.length).split("_").join(".");
487 if (cleanKey[0] !== cleanKey[0].toUpperCase()) {
488 _.update(cfg, cleanKey, () => process.env[key]);
489 }
490 }
491 }
492}
493exports.resolveEnvironmentVariables = resolveEnvironmentVariables;
494/**
495 * Resolve placeholders against the process.env.
496 * Placeholders should be of form ${ENV_VAR}. Placeholders support default values
497 * in case they aren't defined: ${ENV_VAR:default value}
498 */
499function resolvePlaceholders(cfg, replacer = resolvePlaceholder, visited = [cfg.sdm, cfg.local, cfg.events, cfg.commands, cfg.listeners]) {
500 return __awaiter(this, void 0, void 0, function* () {
501 yield resolvePlaceholdersRecursively(cfg, visited, replacer);
502 });
503}
504exports.resolvePlaceholders = resolvePlaceholders;
505function resolvePlaceholdersRecursively(obj, visited, replacer) {
506 return __awaiter(this, void 0, void 0, function* () {
507 if (!visited.includes(obj)) {
508 visited.push(obj);
509 for (const property in obj) {
510 if (_.has(obj, property)) {
511 if (typeof obj[property] === "object") {
512 yield resolvePlaceholdersRecursively(obj[property], visited, replacer);
513 }
514 else if (typeof obj[property] === "string") {
515 obj[property] = yield replacer(obj[property]);
516 }
517 }
518 }
519 }
520 });
521}
522const PlaceholderExpression = /\$\{([.a-zA-Z_-]+)([.:0-9a-zA-Z-_ \" ]+)*\}/g;
523function resolvePlaceholder(value) {
524 return __awaiter(this, void 0, void 0, function* () {
525 if (!PlaceholderExpression.test(value)) {
526 return value;
527 }
528 PlaceholderExpression.lastIndex = 0;
529 let currentValue = value;
530 let result;
531 // tslint:disable-next-line:no-conditional-assignment
532 while (result = PlaceholderExpression.exec(currentValue)) {
533 const fm = result[0];
534 const envValue = process.env[result[1]];
535 const defaultValue = result[2] ? result[2].trim().slice(1) : undefined;
536 if (envValue) {
537 currentValue = currentValue.split(fm).join(envValue);
538 }
539 else if (defaultValue) {
540 currentValue = currentValue.split(fm).join(defaultValue);
541 }
542 else {
543 throw new Error(`Environment variable '${result[1]}' is not defined`);
544 }
545 }
546 return currentValue;
547 });
548}
549/**
550 * Invoke postProcessors on the provided configuration.
551 */
552function invokePostProcessors(cfg) {
553 return cfg.postProcessors.reduce((pp, fp) => pp.then(fp), Promise.resolve(cfg));
554}
555exports.invokePostProcessors = invokePostProcessors;
556/**
557 * Make sure final configuration has the minimum configuration it
558 * needs. It will throw an error if required properties are missing.
559 *
560 * @param cfg final configuration
561 */
562function validateConfiguration(cfg) {
563 if (!cfg) {
564 throw new Error(`no configuration defined`);
565 }
566 const errors = [];
567 if (!cfg.name) {
568 errors.push("you must set a 'name' property in your configuration");
569 }
570 if (!cfg.version) {
571 errors.push("you must set a 'version' property in your configuration");
572 }
573 if (!cfg.apiKey) {
574 logger_1.logger.info("To obtain an 'apiKey' visit https://app.atomist.com/apikeys and run 'atomist config' " +
575 "to configure the apiKey in your local configuration");
576 errors.push("you must set an 'apiKey' property in your configuration");
577 }
578 cfg.workspaceIds = cfg.workspaceIds || [];
579 cfg.groups = cfg.groups || [];
580 if (cfg.workspaceIds.length < 1 && cfg.groups.length < 1) {
581 errors.push("you must either provide an array of 'groups' in your configuration or, more likely, provide " +
582 "an array of 'workspaceIds' in your configuration or set the ATOMIST_WORKSPACES environment variable " +
583 "to a comma-separated list of workspace IDs");
584 }
585 if (cfg.workspaceIds.length > 0 && cfg.groups.length > 0) {
586 errors.push("you cannot specify both 'workspaceIds' and 'groups' in your configuration, you must set one " +
587 "to an empty array");
588 }
589 if (errors.length > 0) {
590 const msg = `Configuration (${stringify(cfg, string_1.obfuscateJson)}) is not correct: ${errors.join("; ")}`;
591 throw new Error(msg);
592 }
593}
594exports.validateConfiguration = validateConfiguration;
595/**
596 * Load and populate the automation configuration. The configuration
597 * is loaded from several locations with the following precedence from
598 * highest to lowest.
599 *
600 * 1. `ATOMIST_` environment variables, see [[resolveEnvironmentVariables]]
601 * 2. Configuration returned from the post-processors.
602 * 3. Recognized environment variables, see [[resolveWorkspaceIds]],
603 * [[resolvePort]]
604 * 4. The value of the ATOMIST_CONFIG environment variable, parsed as
605 * JSON and cast to AutomationServerOptions
606 * 5. The contents of the ATOMIST_CONFIG_PATH file as AutomationServerOptions
607 * 6. The contents of the user's client.config.json as UserConfig
608 * resolving user and per-module configuration into Configuration
609 * 7. The automation atomist.config.js or _all_ index.js files'
610 * configurations exported as `configuration` from those files
611 * 8. ProductionDefaultConfiguration if ATOMIST_ENV or NODE_ENV is set
612 * to "production" or TestingDefaultConfiguration if ATOMIST_ENV or
613 * NODE_ENV is set to "staging" or "testing", with ATOMIST_ENV
614 * taking precedence over NODE_ENV.
615 * 9. LocalDefaultConfiguration
616 *
617 * If any of the sources are missing, they are ignored. Any truthy
618 * configuration values specified by sources of higher precedence
619 * cause any values provided by sources of lower precedence to be
620 * ignored. Arrays are replaced, not merged. Typically the only
621 * required values in the configuration for a successful registration
622 * are the apiKey and non-empty workspaceIds.
623 *
624 * Placeholder of the form `${ENV_VARIABLE}` in string configuration
625 * values will get resolved against the environment. The resolution
626 * happens after all of the above configuration sources have been
627 * merged.
628 *
629 * After all sources are merged and the resulting configuration
630 * processed for placeholders and environment variables, the
631 * configuration is validated using [[validateConfiguration]].
632 *
633 * The configuration exported from the index.js (or atomist.config.js)
634 * is modified to contain the final configuration values and returned
635 * from this function.
636 *
637 * @param base path to file exporting the configuration object, if
638 * not provided the package is searched for one
639 * @return merged configuration object
640 */
641function loadConfiguration(base) {
642 return __awaiter(this, void 0, void 0, function* () {
643 // Register the logger globally so that downstream modules can see it
644 global.__logger = logger_1.logger;
645 const cfgPath = typeof base === "string" ? base : undefined;
646 const cfgBase = typeof base !== "string" ? base : undefined;
647 let cfg;
648 try {
649 const defCfg = defaultConfiguration();
650 const autoCfg = (yield cfgBase) || (yield loadAutomationConfig(cfgPath)) || (yield loadIndexConfig());
651 const userCfg = loadUserConfiguration(defCfg.name, defCfg.version);
652 const atmPathCfg = loadAtomistConfigPath();
653 const atmCfg = loadAtomistConfig();
654 cfg = mergeConfigs({}, defCfg, autoCfg, userCfg, atmPathCfg, atmCfg);
655 resolveWorkspaceIds(cfg);
656 resolvePort(cfg);
657 }
658 catch (e) {
659 e.message = `Failed to load configuration: ${e.message}`;
660 logger_1.logger.error(e.message);
661 if (e.stack) {
662 logger_1.logger.error(`Stack trace:\n${e.stack}`);
663 }
664 throw e;
665 }
666 const completeCfg = yield invokePostProcessors(cfg);
667 completeCfg.postProcessors = [];
668 resolveEnvironmentVariables(completeCfg);
669 yield resolvePlaceholders(completeCfg);
670 validateConfiguration(completeCfg);
671 return completeCfg;
672 });
673}
674exports.loadConfiguration = loadConfiguration;
675/**
676 * Default set of regular expressions used to remove sensitive
677 * information from messages and logs. The entries are applied in
678 * order, so more specific regular expressions should be placed
679 * earlier in the list to avoid a shorter replacement preventing a
680 * longer replacement from being applied.
681 */
682exports.DEFAULT_REDACTION_PATTERNS = [
683 {
684 regexp: /\b[A-F0-9]{64}\b/g,
685 replacement: "[ATOMIST_API_KEY]",
686 },
687 {
688 regexp: /[1-9][0-9]+-[0-9a-zA-Z]{40}/g,
689 replacement: "[TWITTER_ACCESS_TOKEN]",
690 },
691 {
692 regexp: /EAACEdEose0cBA[0-9A-Za-z]+/g,
693 replacement: "[FACEBOOK_ACCESS_TOKEN]",
694 },
695 {
696 regexp: /AIza[0-9A-Za-z\-_]{35}/g,
697 replacement: "[GOOGLE_API_KEY]",
698 },
699 {
700 regexp: /[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com/g,
701 replacement: "[GOOGLE_OAUTH_ID]",
702 },
703 {
704 regexp: /sk_live_[0-9a-z]{32}/g,
705 replacement: "[PICATIC_API_KEY|",
706 },
707 {
708 regexp: /sk_live_[0-9a-zA-Z]{24}/g,
709 replacement: "[STRIPE_REGULAR_API_KEY]",
710 },
711 {
712 regexp: /rk_live_[0-9a-zA-Z]{24}/g,
713 replacement: "[STRIPE_RESTRICTED_API_KEY]",
714 },
715 {
716 regexp: /sq0atp-[0-9A-Za-z\-_]{22}/g,
717 replacement: "[SQUARE_OAUTH_TOKEN]",
718 },
719 {
720 regexp: /sq0csp-[0-9A-Za-z\-_]{43}/g,
721 replacement: "[SQUARE_OAUTH_SECRET]",
722 },
723 {
724 regexp: /access_token\$production\$[0-9a-z]{16}\$[0-9a-f]{32}/g,
725 replacement: "[BRAINTREE_ACCESS_TOKEN]",
726 },
727 {
728 regexp: /amzn\.mws\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g,
729 replacement: "[AMAZON_AUTH_TOKEN]",
730 },
731 {
732 regexp: /SK[0-9a-fA-F]{32}/g,
733 replacement: "[TWILLIO_API_KEY]",
734 },
735 {
736 regexp: /key-[0-9a-zA-Z]{32}/g,
737 replacement: "[MAILGUN_KEY]",
738 },
739 {
740 regexp: /[0-9a-f]{32}-us[0-9]{1,2}/g,
741 replacement: "[MAILCHIMP_API_KEY]",
742 },
743 {
744 regexp: /\bAK[0-9A-Z]{18}\b/g,
745 replacement: "[AMAZON_ACCESS_KEY]",
746 },
747 {
748 regexp: /\b(https?:\/\/)(?:v1\.)?[a-f0-9]{40}((?::x-oauth-basic)?@)/g,
749 replacement: "$1[GITHUB_TOKEN]$2",
750 },
751 {
752 // https://perishablepress.com/stop-using-unsafe-characters-in-urls/
753 // https://www.ietf.org/rfc/rfc3986.txt
754 regexp: /\b((?:ht|f|sm)tps?:\/\/[^:/?#\[\]@""<>{}|\\^``\s]+:)[^:/?#\[\]@""<>{}|\\^``\s]+@/g,
755 replacement: "$1[URL_PASSWORD]@",
756 },
757];
758/**
759 * Default configuration when running in neither testing or
760 * production.
761 */
762exports.LocalDefaultConfiguration = {
763 workspaceIds: [],
764 groups: [],
765 environment: "local",
766 policy: "ephemeral",
767 endpoints: {
768 api: "https://automation.atomist.com/registration",
769 graphql: "https://automation.atomist.com/graphql/team",
770 auth: "https://api.atomist.com/v2/auth",
771 },
772 http: {
773 enabled: true,
774 host: "0.0.0.0",
775 auth: {
776 basic: {
777 enabled: false,
778 },
779 bearer: {
780 enabled: false,
781 },
782 },
783 customizers: [],
784 },
785 ws: {
786 enabled: true,
787 termination: {
788 graceful: false,
789 gracePeriod: shutdown_1.defaultGracePeriod,
790 },
791 compress: false,
792 timeout: 30000,
793 lifecycle: new WebSocketLifecycle_1.QueuingWebSocketLifecycle(),
794 },
795 applicationEvents: {
796 enabled: false,
797 },
798 cluster: {
799 enabled: false,
800 },
801 logging: {
802 level: "info",
803 callsite: false,
804 color: true,
805 file: {
806 enabled: true,
807 level: "debug",
808 },
809 banner: {
810 enabled: true,
811 contributors: [],
812 },
813 },
814 statsd: {
815 enabled: false,
816 },
817 redact: {
818 log: true,
819 messages: true,
820 patterns: exports.DEFAULT_REDACTION_PATTERNS,
821 },
822 commands: undefined,
823 events: undefined,
824 ingesters: [],
825 listeners: [],
826 postProcessors: [],
827};
828/**
829 * Configuration defaults for production environments.
830 */
831exports.ProductionDefaultConfiguration = {
832 environment: "production",
833 policy: "durable",
834 http: {
835 port: 2866,
836 auth: {
837 basic: {
838 enabled: true,
839 },
840 bearer: {
841 enabled: true,
842 },
843 },
844 },
845 ws: {
846 termination: {
847 graceful: true,
848 },
849 compress: true,
850 },
851 applicationEvents: {
852 enabled: true,
853 },
854 cluster: {
855 enabled: true,
856 },
857 logging: {
858 level: "info",
859 file: {
860 enabled: false,
861 },
862 },
863 statsd: {
864 enabled: true,
865 },
866};
867/**
868 * Configuration defaults for pre-production environments.
869 */
870exports.TestingDefaultConfiguration = {
871 environment: "testing",
872 policy: "durable",
873 http: {
874 auth: {
875 basic: {
876 enabled: true,
877 },
878 bearer: {
879 enabled: true,
880 },
881 },
882 },
883 ws: {
884 termination: {
885 graceful: true,
886 },
887 compress: true,
888 },
889 applicationEvents: {
890 enabled: true,
891 },
892 cluster: {
893 enabled: true,
894 },
895 logging: {
896 level: "info",
897 file: {
898 enabled: false,
899 },
900 },
901 statsd: {
902 enabled: true,
903 },
904};
905//# sourceMappingURL=configuration.js.map
\No newline at end of file