From d31d309bc043ac0d036fa1b299a0a541d8b4f804 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 15 Jan 2024 18:36:35 +0800 Subject: [PATCH] MOre on env parsing for k8s --- packages/api/src/env.ts | 2 +- packages/api/src/gcp-utils.ts | 28 ++++++++++++++++++++++++++++ packages/api/src/queue-processor.ts | 2 +- packages/api/src/util.ts | 8 ++++++-- 4 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 packages/api/src/gcp-utils.ts diff --git a/packages/api/src/env.ts b/packages/api/src/env.ts index 7920d2b62..0222de6a9 100755 --- a/packages/api/src/env.ts +++ b/packages/api/src/env.ts @@ -1,6 +1,6 @@ import { getEnv } from './util' -export const env = getEnv() +export const env = getEnv(process.env) export function homePageURL(): string { return env.client.url diff --git a/packages/api/src/gcp-utils.ts b/packages/api/src/gcp-utils.ts new file mode 100644 index 000000000..563f6a30a --- /dev/null +++ b/packages/api/src/gcp-utils.ts @@ -0,0 +1,28 @@ +import { BackendEnv, getEnv } from './util' +import { SecretManagerServiceClient } from '@google-cloud/secret-manager' + +// When running on GCP we want to use secrets manager instead +// of environment variables for storing secrets. This means +// after startup we need to query secret manager, and +// pull in those secrets. +// To opt into this feature you need to set the `GCP_SECRETS_NAME` +// environment variable to the secrets name for example: +// `omnivore-project/secrets/my-secrets/latest` +// +export const loadEnvFromGCPSecrets = async (): Promise< + BackendEnv | undefined +> => { + if (process.env.GCP_SECRETS_NAME && process.env.GCP_PROJECT_ID) { + const client = new SecretManagerServiceClient() + const [version] = await client.accessSecretVersion({ + name: `projects/${process.env.GCP_PROJECT_ID}/secrets/${process.env.GCP_SECRETS_NAME}/versions/latest`, + }) + if (!version || !version.payload || !version.payload.data) { + throw new Error(`no data for secret: ${process.env.GCP_SECRETS_NAME}`) + } + let data = Buffer.from(version.payload.data.toString(), 'base64') + const result: any = JSON.parse(data.toString()) + return getEnv(result) + } + return undefined +} diff --git a/packages/api/src/queue-processor.ts b/packages/api/src/queue-processor.ts index 163e2da36..c59eb9d18 100644 --- a/packages/api/src/queue-processor.ts +++ b/packages/api/src/queue-processor.ts @@ -18,7 +18,7 @@ export const QUEUE_NAME = 'omnivore-backend-queue' const main = async () => { console.log('[queue-processor]: starting queue processor') - let env = (await loadEnvFromGCPSecrets()) ?? getEnv() + let env = (await loadEnvFromGCPSecrets()) ?? getEnv(process.env) const app: Express = express() const port = process.env.PORT || 3002 diff --git a/packages/api/src/util.ts b/packages/api/src/util.ts index 27e26a088..06b403b8d 100755 --- a/packages/api/src/util.ts +++ b/packages/api/src/util.ts @@ -205,11 +205,15 @@ const envParser = ) } -export function getEnv(): BackendEnv { +interface Dict { + [key: string]: T | undefined +} + +export function getEnv(from: Dict): BackendEnv { // Dotenv parses env file merging into proces.env which is then read into custom struct here. dotenv.config() - const parse = envParser(process.env) + const parse = envParser(from) const pg = { host: parse('PG_HOST'), port: parseInt(parse('PG_PORT'), 10),