Skip to content

Commit

Permalink
Serve.js refactors (#158750)
Browse files Browse the repository at this point in the history
Closes #155137, with some extra reorganisation, modularisation and unit
tests.

### Refactors to `maybeAddConfig`

### Refactoring serve.js <-> bootstrap.ts

### Unit tests for `compileConfigStack`
---------

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
delanni and kibanamachine authored Jun 8, 2023
1 parent 74102e5 commit f51f5f4
Show file tree
Hide file tree
Showing 9 changed files with 326 additions and 504 deletions.

This file was deleted.

61 changes: 0 additions & 61 deletions packages/core/root/core-root-server-internal/src/bootstrap.test.ts

This file was deleted.

46 changes: 2 additions & 44 deletions packages/core/root/core-root-server-internal/src/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,9 @@
*/

import chalk from 'chalk';
import { firstValueFrom } from 'rxjs';
import { getPackages } from '@kbn/repo-packages';
import { CliArgs, Env, RawConfigService } from '@kbn/config';
import { CriticalError } from '@kbn/core-base-server-internal';
import { resolve } from 'path';
import { getConfigDirectory } from '@kbn/utils';
import { statSync } from 'fs';
import { VALID_SERVERLESS_PROJECT_TYPES } from './root/serverless_config';
import { Root } from './root';
import { MIGRATION_EXCEPTION_CODE } from './constants';

Expand Down Expand Up @@ -43,40 +38,15 @@ export async function bootstrap({ configs, cliArgs, applyConfigOverrides }: Boot
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { REPO_ROOT } = require('@kbn/repo-info');

let env = Env.createDefault(REPO_ROOT, {
const env = Env.createDefault(REPO_ROOT, {
configs,
cliArgs,
repoPackages: getPackages(REPO_ROOT),
});

let rawConfigService = new RawConfigService(env.configs, applyConfigOverrides);
const rawConfigService = new RawConfigService(env.configs, applyConfigOverrides);
rawConfigService.loadConfig();

// Hack to load the extra serverless config files if `serverless: {projectType}` is found in it.
const rawConfig = await firstValueFrom(rawConfigService.getConfig$());
const serverlessProjectType = rawConfig?.serverless;
if (
typeof serverlessProjectType === 'string' &&
VALID_SERVERLESS_PROJECT_TYPES.includes(serverlessProjectType)
) {
const extendedConfigs = [
...['serverless.yml', `serverless.${serverlessProjectType}.yml`]
.map((name) => resolve(getConfigDirectory(), name))
.filter(configFileExists),
...configs,
];

env = Env.createDefault(REPO_ROOT, {
configs: extendedConfigs,
cliArgs: { ...cliArgs, serverless: true },
repoPackages: getPackages(REPO_ROOT),
});

rawConfigService.stop();
rawConfigService = new RawConfigService(env.configs, applyConfigOverrides);
rawConfigService.loadConfig();
}

const root = new Root(rawConfigService, env, onRootShutdown);
const cliLogger = root.logger.get('cli');

Expand Down Expand Up @@ -160,15 +130,3 @@ function onRootShutdown(reason?: any) {

process.exit(0);
}

function configFileExists(path: string) {
try {
return statSync(path).isFile();
} catch (err) {
if (err.code === 'ENOENT') {
return false;
}

throw err;
}
}
156 changes: 156 additions & 0 deletions src/cli/serve/compile_config_stack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import _ from 'lodash';

import { readFileSync, writeFileSync, statSync, existsSync } from 'fs';
import { resolve } from 'path';
import { getConfigPath, getConfigDirectory } from '@kbn/utils';
import { getConfigFromFiles } from '@kbn/config';

const isNotEmpty = _.negate(_.isEmpty);
const isNotNull = _.negate(_.isNull);

/** @typedef {'es' | 'oblt' | 'security'} ServerlessProjectMode */
/** @type {ServerlessProjectMode[]} */
const VALID_SERVERLESS_PROJECT_MODE = ['es', 'oblt', 'security'];

/**
* Collects paths to configurations to be included in the final configuration stack.
* @param {{configOverrides?: string[], devConfig?: boolean, dev?: boolean, serverless?: string | true}} options Options impacting the outgoing config list
* @returns List of paths to configurations to be merged, from left to right.
*/
export function compileConfigStack({ configOverrides, devConfig, dev, serverless }) {
const cliConfigs = configOverrides || [];
const envConfigs = getEnvConfigs();
const defaultConfig = getConfigPath();

let configs = [cliConfigs, envConfigs, [defaultConfig]].find(isNotEmpty);

if (dev && devConfig !== false) {
configs.push(resolveConfig('kibana.dev.yml'));
}

if (dev && serverless) {
writeProjectSwitcherConfig('serverless.recent.dev.yml', serverless);
configs.push(resolveConfig('serverless.recent.dev.yml'));
}

// Filter out all config paths that didn't exist
configs = configs.filter(isNotNull);

const serverlessMode = validateServerlessMode(serverless) || getServerlessModeFromCfg(configs);
if (serverlessMode) {
configs.unshift(resolveConfig(`serverless.${serverlessMode}.yml`));
configs.unshift(resolveConfig('serverless.yml'));

if (dev && devConfig !== false) {
configs.push(resolveConfig('serverless.dev.yml'));
configs.push(resolveConfig(`serverless.${serverlessMode}.dev.yml`));
}
}

return configs.filter(isNotNull);
}

/**
* @param {string[]} configs List of configuration file paths
* @returns {ServerlessProjectMode|undefined} The serverless mode in the summed configs
*/
function getServerlessModeFromCfg(configs) {
const config = getConfigFromFiles(configs);

return config.serverless;
}

/**
* @param {string} fileName Name of the config within the config directory
* @returns {string | null} The resolved path to the config, if it exists, null otherwise
*/
function resolveConfig(fileName) {
const filePath = resolve(getConfigDirectory(), fileName);
if (fileExists(filePath)) {
return filePath;
} else {
return null;
}
}

/**
* @param {string} fileName
* @param {object} opts
*/
function writeProjectSwitcherConfig(fileName, serverlessOption) {
const path = resolve(getConfigDirectory(), fileName);
const configAlreadyExists = existsSync(path);

const preserveExistingConfig = serverlessOption === true;
const serverlessMode = validateServerlessMode(serverlessOption) || 'es';

if (configAlreadyExists && preserveExistingConfig) {
return;
} else {
const content = `xpack.serverless.plugin.developer.projectSwitcher.enabled: true\nserverless: ${serverlessMode}\n`;
if (!configAlreadyExists || readFileSync(path).toString() !== content) {
writeFileSync(path, content);
}
}
}

/**
* @param {string} filePath Path to the config file
* @returns {boolean} Whether the file exists
*/
function fileExists(filePath) {
try {
return statSync(filePath).isFile();
} catch (err) {
if (err.code === 'ENOENT') {
return false;
}

throw err;
}
}

/**
* @returns {string[]}
*/
function getEnvConfigs() {
const val = process.env.KBN_CONFIG_PATHS;
if (typeof val === 'string') {
return val
.split(',')
.filter((v) => !!v)
.map((p) => resolve(p.trim()));
}
return [];
}

/**
* @param {string | true} serverlessMode
* @returns {ServerlessProjectMode | null}
*/
function validateServerlessMode(serverlessMode) {
if (!serverlessMode) {
return null;
}

if (serverlessMode === true) {
// Defaulting to read the project-switcher's settings in `serverless.recent.dev.yml`
return null;
}

if (VALID_SERVERLESS_PROJECT_MODE.includes(serverlessMode)) {
return serverlessMode;
}

throw new Error(
`invalid --serverless value, must be one of ${VALID_SERVERLESS_PROJECT_MODE.join(', ')}`
);
}
Loading

0 comments on commit f51f5f4

Please sign in to comment.