allow an additional config to be provided to loadBackendConfig during runtime
Signed-off-by: Oleg S <97077423+RobotSail@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/backend-app-api': minor
|
||||
'@backstage/backend-common': minor
|
||||
'@backstage/config-loader': minor
|
||||
---
|
||||
|
||||
Allow an additionalConfig to be provided to loadBackendConfig that fetches config values during runtime.
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
'@backstage/backend-app-api': minor
|
||||
'@backstage/config-loader': minor
|
||||
'@backstage/types': minor
|
||||
---
|
||||
|
||||
Introduces the ability to merge configs and JsonObjects
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
```ts
|
||||
/// <reference types="node" />
|
||||
|
||||
import type { AppConfig } from '@backstage/config';
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
import { CacheClient } from '@backstage/backend-common';
|
||||
import { Config } from '@backstage/config';
|
||||
@@ -178,7 +179,7 @@ export const lifecycleServiceFactory: () => ServiceFactory<
|
||||
export function loadBackendConfig(options: {
|
||||
remote?: LoadConfigOptionsRemote;
|
||||
argv: string[];
|
||||
config?: JsonObject;
|
||||
additionalConfig?: AppConfig;
|
||||
}): Promise<{
|
||||
config: Config;
|
||||
}>;
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
|
||||
import { ConfigService } from '@backstage/backend-plugin-api';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import type { JsonObject, JsonValue } from '@backstage/types';
|
||||
import { mergeJson } from '@backstage/types';
|
||||
import type { JsonValue } from '@backstage/types';
|
||||
|
||||
export class ObservableConfigProxy implements ConfigService {
|
||||
private config: ConfigService = new ConfigReader({});
|
||||
@@ -62,12 +61,6 @@ export class ObservableConfigProxy implements ConfigService {
|
||||
},
|
||||
};
|
||||
}
|
||||
mergeConfig(configData: JsonObject) {
|
||||
if (this.parent) {
|
||||
throw new Error('immutable');
|
||||
}
|
||||
this.setConfig(new ConfigReader(mergeJson(this.config.get(), configData)));
|
||||
}
|
||||
|
||||
private select(required: true): ConfigService;
|
||||
private select(required: false): ConfigService | undefined;
|
||||
|
||||
@@ -24,11 +24,11 @@ import {
|
||||
ConfigTarget,
|
||||
LoadConfigOptionsRemote,
|
||||
} from '@backstage/config-loader';
|
||||
import { type Config, ConfigReader } from '@backstage/config';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import type { Config, AppConfig } from '@backstage/config';
|
||||
import { getPackages } from '@manypkg/get-packages';
|
||||
import { ObservableConfigProxy } from './ObservableConfigProxy';
|
||||
import { isValidUrl } from '../lib/urls';
|
||||
import type { JsonObject } from '@backstage/types';
|
||||
|
||||
/** @public */
|
||||
export async function createConfigSecretEnumerator(options: {
|
||||
@@ -71,7 +71,7 @@ export async function createConfigSecretEnumerator(options: {
|
||||
export async function loadBackendConfig(options: {
|
||||
remote?: LoadConfigOptionsRemote;
|
||||
argv: string[];
|
||||
config?: JsonObject;
|
||||
additionalConfig?: AppConfig;
|
||||
}): Promise<{ config: Config }> {
|
||||
const args = parseArgs(options.argv);
|
||||
|
||||
@@ -115,8 +115,12 @@ export async function loadBackendConfig(options: {
|
||||
`Loaded config from ${appConfigs.map(c => c.context).join(', ')}`,
|
||||
);
|
||||
|
||||
// add the additional config if provided
|
||||
if (options.additionalConfig) {
|
||||
appConfigs.push(options.additionalConfig);
|
||||
}
|
||||
|
||||
config.setConfig(ConfigReader.fromConfigs(appConfigs));
|
||||
config.mergeConfig(options.config ?? {});
|
||||
|
||||
return { config };
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
/// <reference types="webpack-env" />
|
||||
|
||||
import { AwsCredentialsManager } from '@backstage/integration-aws-node';
|
||||
import type { AppConfig } from '@backstage/config';
|
||||
import aws from 'aws-sdk';
|
||||
import { AwsS3Integration } from '@backstage/integration';
|
||||
import { AzureIntegration } from '@backstage/integration';
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
@@ -532,6 +534,7 @@ export const legacyPlugin: (
|
||||
export function loadBackendConfig(options: {
|
||||
logger: LoggerService;
|
||||
remote?: LoadConfigOptionsRemote;
|
||||
additionalConfig?: AppConfig;
|
||||
argv: string[];
|
||||
}): Promise<Config>;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
loadBackendConfig as newLoadBackendConfig,
|
||||
} from '@backstage/backend-app-api';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { Config } from '@backstage/config';
|
||||
import type { AppConfig, Config } from '@backstage/config';
|
||||
import { LoadConfigOptionsRemote } from '@backstage/config-loader';
|
||||
import { setRootLoggerRedactionList } from './logging/createRootLogger';
|
||||
|
||||
@@ -34,6 +34,7 @@ export async function loadBackendConfig(options: {
|
||||
logger: LoggerService;
|
||||
// process.argv or any other overrides
|
||||
remote?: LoadConfigOptionsRemote;
|
||||
additionalConfig?: AppConfig;
|
||||
argv: string[];
|
||||
}): Promise<Config> {
|
||||
const secretEnumerator = await createConfigSecretEnumerator({
|
||||
|
||||
@@ -29,9 +29,6 @@ export type JsonPrimitive = number | string | boolean | null;
|
||||
// @public
|
||||
export type JsonValue = JsonObject | JsonArray | JsonPrimitive;
|
||||
|
||||
// @public
|
||||
export const mergeJson: (a: JsonObject, b: JsonObject) => JsonObject;
|
||||
|
||||
// @public
|
||||
export type Observable<T> = {
|
||||
[Symbol.observable](): Observable<T>;
|
||||
|
||||
@@ -21,6 +21,5 @@
|
||||
*/
|
||||
|
||||
export type { JsonArray, JsonObject, JsonPrimitive, JsonValue } from './json';
|
||||
export { mergeJson } from './json';
|
||||
export type { Observable, Observer, Subscription } from './observable';
|
||||
export type { HumanDuration } from './time';
|
||||
|
||||
@@ -14,13 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
JsonPrimitive,
|
||||
JsonArray,
|
||||
JsonObject,
|
||||
JsonValue,
|
||||
mergeJson,
|
||||
} from './json';
|
||||
import { JsonPrimitive, JsonArray, JsonObject, JsonValue } from './json';
|
||||
|
||||
describe('json', () => {
|
||||
it('JsonPrimitive', () => {
|
||||
@@ -85,124 +79,3 @@ describe('json', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('jsonMerge', () => {
|
||||
it('should merge two objects', () => {
|
||||
const obj1 = { a: 1, b: 2, c: 3 };
|
||||
const obj2 = { b: 4, c: 5, d: 6 };
|
||||
const merged = mergeJson(obj1, obj2);
|
||||
expect(merged).toEqual({ a: 1, b: 4, c: 5, d: 6 });
|
||||
});
|
||||
|
||||
it('should always prefer to merge the values of the second parameter', () => {
|
||||
const obj1 = {
|
||||
a: 1,
|
||||
b: [1, 2, 3],
|
||||
c: {
|
||||
z: 1,
|
||||
y: 2,
|
||||
x: 3,
|
||||
},
|
||||
};
|
||||
const obj2 = {
|
||||
a: 2,
|
||||
b: [2, 4, 6],
|
||||
c: {
|
||||
z: 2,
|
||||
y: 4,
|
||||
x: 6,
|
||||
},
|
||||
};
|
||||
const merged = mergeJson(obj1, obj2);
|
||||
expect(merged).toEqual(obj2);
|
||||
});
|
||||
|
||||
it('should prefer the second argument whenever keys collide', () => {
|
||||
const obj1 = {
|
||||
a: 1,
|
||||
b: [1, 2, 3],
|
||||
c: {
|
||||
z: 1,
|
||||
y: 2,
|
||||
x: 3,
|
||||
},
|
||||
};
|
||||
const obj2 = {
|
||||
a: 2,
|
||||
c: {
|
||||
y: 4,
|
||||
},
|
||||
};
|
||||
const merged = mergeJson(obj1, obj2);
|
||||
expect(merged).toEqual({
|
||||
a: 2,
|
||||
b: [1, 2, 3],
|
||||
c: {
|
||||
z: 1,
|
||||
y: 4,
|
||||
x: 3,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should merge recursively', () => {
|
||||
const obj1 = {
|
||||
backend: {
|
||||
database: {
|
||||
provider: 'sqlite3',
|
||||
},
|
||||
},
|
||||
};
|
||||
const obj2 = {
|
||||
backend: {
|
||||
database: {
|
||||
password: 'password123',
|
||||
},
|
||||
},
|
||||
};
|
||||
const merged = mergeJson(obj1, obj2);
|
||||
expect(merged).toEqual({
|
||||
backend: {
|
||||
database: {
|
||||
provider: 'sqlite3',
|
||||
password: 'password123',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should overwrite the array with the second argument's array", () => {
|
||||
const obj1 = {
|
||||
array: ['a', 'b', 'c'],
|
||||
};
|
||||
const obj2 = {
|
||||
array: [1, 2, 3],
|
||||
};
|
||||
const merged = mergeJson(obj1, obj2);
|
||||
expect(merged).toEqual({
|
||||
array: [1, 2, 3],
|
||||
});
|
||||
|
||||
const merged2 = mergeJson(obj2, obj1);
|
||||
expect(merged2).toEqual({
|
||||
array: ['a', 'b', 'c'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should take only the defined value in the case of a collision', () => {
|
||||
const obj1 = {
|
||||
sqlite: undefined,
|
||||
};
|
||||
const obj2 = {
|
||||
sqlite: 'sqlite3',
|
||||
};
|
||||
const merged = mergeJson(obj1, obj2);
|
||||
expect(merged).toEqual({
|
||||
sqlite: 'sqlite3',
|
||||
});
|
||||
const merged2 = mergeJson(obj2, obj1);
|
||||
expect(merged2).toEqual({
|
||||
sqlite: 'sqlite3',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,62 +41,3 @@ export interface JsonArray extends Array<JsonValue> {}
|
||||
* @public
|
||||
*/
|
||||
export type JsonValue = JsonObject | JsonArray | JsonPrimitive;
|
||||
|
||||
/**
|
||||
* Attempts to merge two JsonObjects together. In the case of collisions, this function
|
||||
* prefers values from b, unless the value is an object, in which case it recursively
|
||||
* merges the values.
|
||||
*
|
||||
* @param a - The base object
|
||||
* @param b - The object to merge into a
|
||||
* @returns The merged object
|
||||
* @public
|
||||
*/
|
||||
export const mergeJson = (a: JsonObject, b: JsonObject): JsonObject => {
|
||||
const final: JsonObject = {};
|
||||
const bKeys = new Set(Object.keys(b));
|
||||
const aKeys = new Set(Object.keys(a));
|
||||
const intersectingKeys = new Set([...aKeys].filter(x => bKeys.has(x)));
|
||||
|
||||
// add all mutually exclusive keys to the final object
|
||||
for (const key of aKeys.values()) {
|
||||
if (!intersectingKeys.has(key)) {
|
||||
final[key] = a[key];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (const key of bKeys.values()) {
|
||||
if (!intersectingKeys.has(key)) {
|
||||
final[key] = b[key];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// values now are all overlapping and are either primitives, arrays, or objects.
|
||||
// for all primitives and arrays, we want to assign the value from b
|
||||
// for all objects, we want to recursively merge the values
|
||||
for (const key of intersectingKeys.values()) {
|
||||
// check if either value is undefined and default to the defined one
|
||||
const aValue = a[key];
|
||||
const bValue = b[key];
|
||||
if (!aValue) {
|
||||
final[key] = bValue;
|
||||
continue;
|
||||
}
|
||||
if (!bValue) {
|
||||
final[key] = aValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if value is an array or primitive
|
||||
const value = bValue;
|
||||
if (Array.isArray(value) || typeof value !== 'object') {
|
||||
final[key] = value;
|
||||
continue;
|
||||
}
|
||||
|
||||
// recursively merge the values
|
||||
final[key] = mergeJson(aValue as JsonObject, bValue as JsonObject);
|
||||
}
|
||||
return final;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user