feat(home-server): add overriding environment variables in underlying services (#621)

This commit is contained in:
Karol Sójko
2023-06-02 10:48:17 +02:00
committed by GitHub
parent 8c1f583968
commit f0cbec07b8
31 changed files with 144 additions and 247 deletions

1
.pnp.cjs generated
View File

@@ -4802,6 +4802,7 @@ const RAW_RUNTIME_STATE =
"packageDependencies": [\
["@standardnotes/event-store", "workspace:packages/event-store"],\
["@aws-sdk/client-sqs", "npm:3.342.0"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/time", "workspace:packages/time"],\

View File

@@ -1,24 +1,9 @@
import { AbstractEnv } from '@standardnotes/domain-core'
import { config, DotenvParseOutput } from 'dotenv'
import { injectable } from 'inversify'
@injectable()
export class Env {
private env?: DotenvParseOutput
public load(): void {
export class Env extends AbstractEnv {
load(): void {
const output = config()
this.env = <DotenvParseOutput>output.parsed
}
public get(key: string, optional = false): string {
if (!this.env) {
this.load()
}
if (!process.env[key] && !optional) {
throw new Error(`Environment variable ${key} not set`)
}
return <string>process.env[key]
}
}

View File

@@ -24,8 +24,12 @@ import { OptionalCrossServiceTokenMiddleware } from '../Controller/OptionalCross
import { Transform } from 'stream'
export class ContainerConfigLoader {
async load(configuration?: { serviceContainer?: ServiceContainerInterface; logger?: Transform }): Promise<Container> {
const env: Env = new Env()
async load(configuration?: {
serviceContainer?: ServiceContainerInterface
logger?: Transform
environmentOverrides?: { [name: string]: string }
}): Promise<Container> {
const env: Env = new Env(configuration?.environmentOverrides)
env.load()
const container = new Container()

View File

@@ -1,24 +1,9 @@
import { AbstractEnv } from '@standardnotes/domain-core'
import { config, DotenvParseOutput } from 'dotenv'
import { injectable } from 'inversify'
@injectable()
export class Env {
private env?: DotenvParseOutput
public load(): void {
export class Env extends AbstractEnv {
load(): void {
const output = config()
this.env = <DotenvParseOutput>output.parsed
}
public get(key: string, optional = false): string {
if (!this.env) {
this.load()
}
if (!process.env[key] && !optional) {
throw new Error(`Environment variable ${key} not set`)
}
return <string>process.env[key]
}
}

View File

@@ -1,29 +1,28 @@
import { ServiceContainerInterface, ServiceIdentifier, ServiceInterface } from '@standardnotes/domain-core'
import {
ServiceConfiguration,
ServiceContainerInterface,
ServiceIdentifier,
ServiceInterface,
} from '@standardnotes/domain-core'
import { ContainerConfigLoader } from './Container'
import { Transform } from 'stream'
export class Service implements ServiceInterface {
private logger: Transform | undefined
constructor(private serviceContainer: ServiceContainerInterface) {
this.serviceContainer.register(this.getId(), this)
}
setLogger(logger: Transform): void {
this.logger = logger
}
async handleRequest(_request: never, _response: never, _endpointOrMethodIdentifier: string): Promise<unknown> {
throw new Error('Requests are handled via inversify-express at ApiGateway level')
}
async getContainer(): Promise<unknown> {
async getContainer(configuration?: ServiceConfiguration): Promise<unknown> {
const config = new ContainerConfigLoader()
return config.load({
serviceContainer: this.serviceContainer,
logger: this.logger,
logger: configuration?.logger,
environmentOverrides: configuration?.environmentOverrides,
})
}

View File

@@ -257,11 +257,12 @@ export class ContainerConfigLoader {
controllerConatiner?: ControllerContainerInterface
directCallDomainEventPublisher?: DirectCallDomainEventPublisher
logger?: Transform
environmentOverrides?: { [name: string]: string }
}): Promise<Container> {
const directCallDomainEventPublisher =
configuration?.directCallDomainEventPublisher ?? new DirectCallDomainEventPublisher()
const env: Env = new Env()
const env: Env = new Env(configuration?.environmentOverrides)
env.load()
const container = new Container()

View File

@@ -1,24 +1,9 @@
import { AbstractEnv } from '@standardnotes/domain-core'
import { config, DotenvParseOutput } from 'dotenv'
import { injectable } from 'inversify'
@injectable()
export class Env {
private env?: DotenvParseOutput
public load(): void {
export class Env extends AbstractEnv {
load(): void {
const output = config()
this.env = <DotenvParseOutput>output.parsed
}
public get(key: string, optional = false): string {
if (!this.env) {
this.load()
}
if (!process.env[key] && !optional) {
throw new Error(`Environment variable ${key} not set`)
}
return <string>process.env[key]
}
}

View File

@@ -1,5 +1,6 @@
import {
ControllerContainerInterface,
ServiceConfiguration,
ServiceContainerInterface,
ServiceIdentifier,
ServiceInterface,
@@ -7,11 +8,8 @@ import {
import { ContainerConfigLoader } from './Container'
import { DirectCallDomainEventPublisher } from '@standardnotes/domain-events-infra'
import { Transform } from 'stream'
export class Service implements ServiceInterface {
private logger: Transform | undefined
constructor(
private serviceContainer: ServiceContainerInterface,
private controllerContainer: ControllerContainerInterface,
@@ -20,10 +18,6 @@ export class Service implements ServiceInterface {
this.serviceContainer.register(this.getId(), this)
}
setLogger(logger: Transform): void {
this.logger = logger
}
async handleRequest(request: never, response: never, endpointOrMethodIdentifier: string): Promise<unknown> {
const method = this.controllerContainer.get(endpointOrMethodIdentifier)
@@ -34,13 +28,14 @@ export class Service implements ServiceInterface {
return method(request, response)
}
async getContainer(): Promise<unknown> {
async getContainer(configuration?: ServiceConfiguration): Promise<unknown> {
const config = new ContainerConfigLoader()
return config.load({
controllerConatiner: this.controllerContainer,
directCallDomainEventPublisher: this.directCallDomainEventPublisher,
logger: this.logger,
logger: configuration?.logger,
environmentOverrides: configuration?.environmentOverrides,
})
}

View File

@@ -0,0 +1,26 @@
export abstract class AbstractEnv {
protected env?: { [key: string]: string } = {}
protected overrides: { [key: string]: string }
constructor(overrides: { [key: string]: string } = {}) {
this.overrides = overrides
}
abstract load(): void
get(key: string, optional = false): string {
if (!this.env) {
this.load()
}
if (this.overrides[key]) {
return this.overrides[key]
}
if (!process.env[key] && !optional) {
throw new Error(`Environment variable ${key} not set`)
}
return <string>process.env[key]
}
}

View File

@@ -0,0 +1,6 @@
import { Transform } from 'stream'
export interface ServiceConfiguration {
logger?: Transform
environmentOverrides?: { [name: string]: string }
}

View File

@@ -1,9 +1,8 @@
import { ServiceConfiguration } from './ServiceConfiguration'
import { ServiceIdentifier } from './ServiceIdentifier'
import { Transform } from 'stream'
export interface ServiceInterface {
getContainer(): Promise<unknown>
setLogger(logger: Transform): void
getContainer(configuration?: ServiceConfiguration): Promise<unknown>
getId(): ServiceIdentifier
handleRequest(request: never, response: never, endpointOrMethodIdentifier: string): Promise<unknown>
}

View File

@@ -37,8 +37,11 @@ export * from './DI/ControllerContainerInterface'
export * from './Email/EmailLevel'
export * from './Email/EmailLevelProps'
export * from './Env/AbstractEnv'
export * from './Mapping/MapperInterface'
export * from './Service/ServiceConfiguration'
export * from './Service/ServiceContainer'
export * from './Service/ServiceContainerInterface'
export * from './Service/ServiceIdentifier'

View File

@@ -33,6 +33,7 @@
},
"dependencies": {
"@aws-sdk/client-sqs": "^3.332.0",
"@standardnotes/domain-core": "workspace:^",
"@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*",
"@standardnotes/time": "workspace:*",

View File

@@ -1,24 +1,9 @@
import { AbstractEnv } from '@standardnotes/domain-core'
import { config, DotenvParseOutput } from 'dotenv'
import { injectable } from 'inversify'
@injectable()
export class Env {
private env?: DotenvParseOutput
public load(): void {
export class Env extends AbstractEnv {
load(): void {
const output = config()
this.env = <DotenvParseOutput>output.parsed
}
public get(key: string, optional = false): string {
if (!this.env) {
this.load()
}
if (!process.env[key] && !optional) {
throw new Error(`Environment variable ${key} not set`)
}
return <string>process.env[key]
}
}

View File

@@ -53,11 +53,12 @@ export class ContainerConfigLoader {
async load(configuration?: {
directCallDomainEventPublisher?: DirectCallDomainEventPublisher
logger?: Transform
environmentOverrides?: { [name: string]: string }
}): Promise<Container> {
const directCallDomainEventPublisher =
configuration?.directCallDomainEventPublisher ?? new DirectCallDomainEventPublisher()
const env: Env = new Env()
const env: Env = new Env(configuration?.environmentOverrides)
env.load()
const container = new Container()

View File

@@ -1,24 +1,9 @@
import { AbstractEnv } from '@standardnotes/domain-core'
import { config, DotenvParseOutput } from 'dotenv'
import { injectable } from 'inversify'
@injectable()
export class Env {
private env?: DotenvParseOutput
public load(): void {
export class Env extends AbstractEnv {
load(): void {
const output = config()
this.env = <DotenvParseOutput>output.parsed
}
public get(key: string, optional = false): string {
if (!this.env) {
this.load()
}
if (!process.env[key] && !optional) {
throw new Error(`Environment variable ${key} not set`)
}
return <string>process.env[key]
}
}

View File

@@ -1,12 +1,14 @@
import { ServiceContainerInterface, ServiceIdentifier, ServiceInterface } from '@standardnotes/domain-core'
import {
ServiceConfiguration,
ServiceContainerInterface,
ServiceIdentifier,
ServiceInterface,
} from '@standardnotes/domain-core'
import { DirectCallDomainEventPublisher } from '@standardnotes/domain-events-infra'
import { ContainerConfigLoader } from './Container'
import { Transform } from 'stream'
export class Service implements ServiceInterface {
private logger: Transform | undefined
constructor(
private serviceContainer: ServiceContainerInterface,
private directCallDomainEventPublisher: DirectCallDomainEventPublisher,
@@ -14,20 +16,17 @@ export class Service implements ServiceInterface {
this.serviceContainer.register(this.getId(), this)
}
setLogger(logger: Transform): void {
this.logger = logger
}
async handleRequest(_request: never, _response: never, _endpointOrMethodIdentifier: string): Promise<unknown> {
throw new Error('Requests are handled via inversify-express at ApiGateway level')
}
async getContainer(): Promise<unknown> {
async getContainer(configuration?: ServiceConfiguration): Promise<unknown> {
const config = new ContainerConfigLoader()
return config.load({
directCallDomainEventPublisher: this.directCallDomainEventPublisher,
logger: this.logger,
logger: configuration?.logger,
environmentOverrides: configuration?.environmentOverrides,
})
}

View File

@@ -1,24 +1,9 @@
import { AbstractEnv } from '@standardnotes/domain-core'
import { config, DotenvParseOutput } from 'dotenv'
import { injectable } from 'inversify'
@injectable()
export class Env {
private env?: DotenvParseOutput
public load(): void {
export class Env extends AbstractEnv {
load(): void {
const output = config()
this.env = <DotenvParseOutput>output.parsed
}
public get(key: string, optional = false): string {
if (!this.env) {
this.load()
}
if (!process.env[key] && !optional) {
throw new Error(`Environment variable ${key} not set`)
}
return <string>process.env[key]
}
}

View File

@@ -20,37 +20,48 @@ const robots = require('express-robots-txt')
import { Env } from '../Bootstrap/Env'
import { HomeServerInterface } from './HomeServerInterface'
import { HomeServerConfiguration } from './HomeServerConfiguration'
export class HomeServer implements HomeServerInterface {
private serverInstance: http.Server | undefined
async start(): Promise<void> {
async start(configuration?: HomeServerConfiguration): Promise<void> {
const controllerContainer = new ControllerContainer()
const serviceContainer = new ServiceContainer()
const directCallDomainEventPublisher = new DirectCallDomainEventPublisher()
const env: Env = new Env()
const env: Env = new Env(configuration?.environment)
env.load()
this.configureLoggers(env)
const apiGatewayService = new ApiGatewayService(serviceContainer)
apiGatewayService.setLogger(winston.loggers.get('api-gateway'))
const authService = new AuthService(serviceContainer, controllerContainer, directCallDomainEventPublisher)
authService.setLogger(winston.loggers.get('auth-server'))
const syncingService = new SyncingService(serviceContainer, controllerContainer, directCallDomainEventPublisher)
syncingService.setLogger(winston.loggers.get('syncing-server'))
const revisionsService = new RevisionsService(serviceContainer, controllerContainer, directCallDomainEventPublisher)
revisionsService.setLogger(winston.loggers.get('revisions-server'))
const filesService = new FilesService(serviceContainer, directCallDomainEventPublisher)
filesService.setLogger(winston.loggers.get('files-server'))
const container = Container.merge(
(await apiGatewayService.getContainer()) as Container,
(await authService.getContainer()) as Container,
(await syncingService.getContainer()) as Container,
(await revisionsService.getContainer()) as Container,
(await filesService.getContainer()) as Container,
(await apiGatewayService.getContainer({
logger: winston.loggers.get('api-gateway'),
environmentOverrides: configuration?.environment,
})) as Container,
(await authService.getContainer({
logger: winston.loggers.get('auth-server'),
environmentOverrides: configuration?.environment,
})) as Container,
(await syncingService.getContainer({
logger: winston.loggers.get('syncing-server'),
environmentOverrides: configuration?.environment,
})) as Container,
(await revisionsService.getContainer({
logger: winston.loggers.get('revisions-server'),
environmentOverrides: configuration?.environment,
})) as Container,
(await filesService.getContainer({
logger: winston.loggers.get('files-server'),
environmentOverrides: configuration?.environment,
})) as Container,
)
const server = new InversifyExpressServer(container)

View File

@@ -0,0 +1,3 @@
export interface HomeServerConfiguration {
environment: { [name: string]: string }
}

View File

@@ -1,5 +1,7 @@
import { HomeServerConfiguration } from './HomeServerConfiguration'
export interface HomeServerInterface {
start(): Promise<void>
start(configuration?: HomeServerConfiguration): Promise<void>
stop(): Promise<void>
restart(): Promise<void>
isRunning(): Promise<boolean>

View File

@@ -1,2 +1,3 @@
export * from './HomeServer'
export * from './HomeServerConfiguration'
export * from './HomeServerInterface'

View File

@@ -53,11 +53,12 @@ export class ContainerConfigLoader {
controllerConatiner?: ControllerContainerInterface
directCallDomainEventPublisher?: DirectCallDomainEventPublisher
logger?: Transform
environmentOverrides?: { [name: string]: string }
}): Promise<Container> {
const directCallDomainEventPublisher =
configuration?.directCallDomainEventPublisher ?? new DirectCallDomainEventPublisher()
const env: Env = new Env()
const env: Env = new Env(configuration?.environmentOverrides)
env.load()
const isConfiguredForHomeServer = env.get('DB_TYPE') === 'sqlite'

View File

@@ -1,24 +1,9 @@
import { AbstractEnv } from '@standardnotes/domain-core'
import { config, DotenvParseOutput } from 'dotenv'
import { injectable } from 'inversify'
@injectable()
export class Env {
private env?: DotenvParseOutput
public load(): void {
export class Env extends AbstractEnv {
load(): void {
const output = config()
this.env = <DotenvParseOutput>output.parsed
}
public get(key: string, optional = false): string {
if (!this.env) {
this.load()
}
if (!process.env[key] && !optional) {
throw new Error(`Environment variable ${key} not set`)
}
return <string>process.env[key]
}
}

View File

@@ -1,5 +1,6 @@
import {
ControllerContainerInterface,
ServiceConfiguration,
ServiceContainerInterface,
ServiceIdentifier,
ServiceInterface,
@@ -7,11 +8,8 @@ import {
import { ContainerConfigLoader } from './Container'
import { DirectCallDomainEventPublisher } from '@standardnotes/domain-events-infra'
import { Transform } from 'stream'
export class Service implements ServiceInterface {
private logger: Transform | undefined
constructor(
private serviceContainer: ServiceContainerInterface,
private controllerContainer: ControllerContainerInterface,
@@ -20,10 +18,6 @@ export class Service implements ServiceInterface {
this.serviceContainer.register(this.getId(), this)
}
setLogger(logger: Transform): void {
this.logger = logger
}
async handleRequest(request: never, response: never, endpointOrMethodIdentifier: string): Promise<unknown> {
const method = this.controllerContainer.get(endpointOrMethodIdentifier)
@@ -34,13 +28,14 @@ export class Service implements ServiceInterface {
return method(request, response)
}
async getContainer(): Promise<unknown> {
async getContainer(configuration?: ServiceConfiguration): Promise<unknown> {
const config = new ContainerConfigLoader()
return config.load({
controllerConatiner: this.controllerContainer,
directCallDomainEventPublisher: this.directCallDomainEventPublisher,
logger: this.logger,
logger: configuration?.logger,
environmentOverrides: configuration?.environmentOverrides,
})
}

View File

@@ -1,24 +1,9 @@
import { AbstractEnv } from '@standardnotes/domain-core'
import { config, DotenvParseOutput } from 'dotenv'
import { injectable } from 'inversify'
@injectable()
export class Env {
private env?: DotenvParseOutput
public load(): void {
export class Env extends AbstractEnv {
load(): void {
const output = config()
this.env = <DotenvParseOutput>output.parsed
}
public get(key: string, optional = false): string {
if (!this.env) {
this.load()
}
if (!process.env[key] && !optional) {
throw new Error(`Environment variable ${key} not set`)
}
return <string>process.env[key]
}
}

View File

@@ -83,11 +83,12 @@ export class ContainerConfigLoader {
controllerConatiner?: ControllerContainerInterface
directCallDomainEventPublisher?: DirectCallDomainEventPublisher
logger?: Transform
environmentOverrides?: { [name: string]: string }
}): Promise<Container> {
const directCallDomainEventPublisher =
configuration?.directCallDomainEventPublisher ?? new DirectCallDomainEventPublisher()
const env: Env = new Env()
const env: Env = new Env(configuration?.environmentOverrides)
env.load()
const container = new Container({

View File

@@ -1,22 +1,9 @@
import { AbstractEnv } from '@standardnotes/domain-core'
import { config, DotenvParseOutput } from 'dotenv'
export class Env {
private env?: DotenvParseOutput
public load(): void {
export class Env extends AbstractEnv {
load(): void {
const output = config()
this.env = <DotenvParseOutput>output.parsed
}
public get(key: string, optional = false): string {
if (!this.env) {
this.load()
}
if (!process.env[key] && !optional) {
throw new Error(`Environment variable ${key} not set`)
}
return <string>process.env[key]
}
}

View File

@@ -1,5 +1,6 @@
import {
ControllerContainerInterface,
ServiceConfiguration,
ServiceContainerInterface,
ServiceIdentifier,
ServiceInterface,
@@ -7,11 +8,8 @@ import {
import { ContainerConfigLoader } from './Container'
import { DirectCallDomainEventPublisher } from '@standardnotes/domain-events-infra'
import { Transform } from 'stream'
export class Service implements ServiceInterface {
private logger: Transform | undefined
constructor(
private serviceContainer: ServiceContainerInterface,
private controllerContainer: ControllerContainerInterface,
@@ -20,10 +18,6 @@ export class Service implements ServiceInterface {
this.serviceContainer.register(this.getId(), this)
}
setLogger(logger: Transform): void {
this.logger = logger
}
async handleRequest(request: never, response: never, endpointOrMethodIdentifier: string): Promise<unknown> {
const method = this.controllerContainer.get(endpointOrMethodIdentifier)
@@ -34,13 +28,14 @@ export class Service implements ServiceInterface {
return method(request, response)
}
async getContainer(): Promise<unknown> {
async getContainer(configuration?: ServiceConfiguration): Promise<unknown> {
const config = new ContainerConfigLoader()
return config.load({
controllerConatiner: this.controllerContainer,
directCallDomainEventPublisher: this.directCallDomainEventPublisher,
logger: this.logger,
logger: configuration?.logger,
environmentOverrides: configuration?.environmentOverrides,
})
}

View File

@@ -1,24 +1,9 @@
import { AbstractEnv } from '@standardnotes/domain-core'
import { config, DotenvParseOutput } from 'dotenv'
import { injectable } from 'inversify'
@injectable()
export class Env {
private env?: DotenvParseOutput
public load(): void {
export class Env extends AbstractEnv {
load(): void {
const output = config()
this.env = <DotenvParseOutput>output.parsed
}
public get(key: string, optional = false): string {
if (!this.env) {
this.load()
}
if (!process.env[key] && !optional) {
throw new Error(`Environment variable ${key} not set`)
}
return <string>process.env[key]
}
}

View File

@@ -3706,6 +3706,7 @@ __metadata:
resolution: "@standardnotes/event-store@workspace:packages/event-store"
dependencies:
"@aws-sdk/client-sqs": "npm:^3.332.0"
"@standardnotes/domain-core": "workspace:^"
"@standardnotes/domain-events": "workspace:*"
"@standardnotes/domain-events-infra": "workspace:*"
"@standardnotes/time": "workspace:*"