Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: config loading #1955

Merged
merged 1 commit into from
Jun 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion .env.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,19 @@ DATABASE_URL=http://localhost:3000
DATABASE_TOKEN=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTYwMzk2ODgzNCwiZXhwIjoyNTUwNjUzNjM0LCJyb2xlIjoic2VydmljZV9yb2xlIn0.necIJaiP7X2T2QjGeV-FhpkizcNTX8HjDDBAxpgQTEI

# Postgres Database
DATABASE_CONNECTION=postgresql://postgres:postgres@localhost:5432/postgres
DATABASE_CONNECTION=postgresql://postgres:postgres@localhost:5432/postgres

# Cluster
CLUSTER_BASIC_AUTH_TOKEN = dGVzdDp0ZXN0
CLUSTER_SERVICE =
CLUSTER_API_URL = http://localhost:9094

# Maintenance Mode
MAINTENANCE_MODE = rw

# S3
S3_ENDPOINT = http://localhost:9095
S3_REGION = test
S3_ACCESS_KEY_ID = test
S3_SECRET_ACCESS_KEY = test
S3_BUCKET_NAME = test
2 changes: 2 additions & 0 deletions packages/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ wrangler secret put S3_ACCESS_KEY_ID --env production # Get from Amazon S3 (not
wrangler secret put S3_SECRET_ACCESS_KEY --env production # Get from Amazon S3 (not required for dev)
wrangler secret put S3_BUCKET_NAME --env production # e.g nft.storage-staging-us-east-2 (not required for dev)
wrangler secret put PRIVATE_KEY --env production # Get from 1password
wrangler secret put MAINTENANCE_MODE --env production # default value is "rw"

wrangler publish --env production
```

Expand Down
23 changes: 15 additions & 8 deletions packages/api/pw-test.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,23 @@ const nodeBuiltinsPlugin = {
},
}

const config = {
inject: [
path.join(__dirname, './scripts/node-globals.js'),
path.join(__dirname, './test/scripts/globals.js'),
],
define: {
NFT_STORAGE_VERSION: JSON.stringify('0.1.0'),
NFT_STORAGE_COMMITHASH: JSON.stringify('322332'),
NFT_STORAGE_BRANCH: JSON.stringify('main'),
},
plugins: [nodeBuiltinsPlugin],
}

/** @type {import('playwright-test').RunnerOptions} */
module.exports = {
buildConfig: {
inject: [path.join(__dirname, './scripts/node-globals.js')],
plugins: [nodeBuiltinsPlugin],
},
buildSWConfig: {
inject: [path.join(__dirname, './scripts/node-globals.js')],
plugins: [nodeBuiltinsPlugin],
},
buildConfig: config,
buildSWConfig: config,
beforeTests: async () => {
const mock = await startMockServer('AWS S3', 9095, 'test/mocks/aws-s3')

Expand Down
6 changes: 3 additions & 3 deletions packages/api/src/bindings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ export interface ServiceConfiguration {
ENV: RuntimeEnvironmentName

/** Semantic version for current build */
NFT_STORAGE_VERSION: string
VERSION: string

/** Git branch name of current build */
NFT_STORAGE_BRANCH: string
BRANCH: string

/** Git commit hash of current build */
NFT_STORAGE_COMMITHASH: string
COMMITHASH: string

/** Current maintenance mode */
MAINTENANCE_MODE: Mode
Expand Down
114 changes: 45 additions & 69 deletions packages/api/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,6 @@ import {
* @typedef {import('./bindings').RuntimeEnvironmentName} RuntimeEnvironmentName
*/

/**
* Default configuration values to be used in test and dev if no explicit definition is found.
*
* @type Record<string, string>
*/
export const DEFAULT_CONFIG_VALUES = {
SALT: 'secret',
DEBUG: 'true',
DATABASE_URL: 'http://localhost:3000',
DATABASE_TOKEN:
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTYwMzk2ODgzNCwiZXhwIjoyNTUwNjUzNjM0LCJyb2xlIjoic2VydmljZV9yb2xlIn0.necIJaiP7X2T2QjGeV-FhpkizcNTX8HjDDBAxpgQTEI',
CLUSTER_API_URL: 'http://localhost:9094',
CLUSTER_BASIC_AUTH_TOKEN: 'dGVzdDp0ZXN0', // test:test
MAGIC_SECRET_KEY: 'test',
ENV: 'test',
SENTRY_DSN: 'https://test@test.ingest.sentry.io/0000000',
NFT_STORAGE_BRANCH: 'test',
NFT_STORAGE_VERSION: 'test',
NFT_STORAGE_COMMITHASH: 'test',
MAINTENANCE_MODE: 'rw',
METAPLEX_AUTH_TOKEN: 'metaplex-test-token',
MAILCHIMP_API_KEY: '',
LOGTAIL_TOKEN: '',
S3_ENDPOINT: 'http://localhost:9095',
S3_REGION: 'test',
S3_ACCESS_KEY_ID: 'test',
S3_SECRET_ACCESS_KEY: 'test',
S3_BUCKET_NAME: 'test',
PRIVATE_KEY: 'xmbtWjE9eYuAxae9G65lQSkw36HV6H+0LSFq2aKqVwY=',
}

/**
* If the CLUSTER_SERVICE variable is set, the service URL will be resolved from here.
*
Expand All @@ -49,12 +18,6 @@ const CLUSTER_SERVICE_URLS = {
IpfsCluster3: 'https://nft3.storage.ipfscluster.io/api/',
}

/**
* @param {RuntimeEnvironmentName} env
* @returns {boolean} true if the named runtime environment should fallback to values from DEFAULT_CONFIG_VALUES if no config var is present.
*/
const allowDefaultConfigValues = (env) => env === 'test' || env === 'dev'

/** @type ServiceConfiguration|undefined */
let _globalConfig

Expand Down Expand Up @@ -90,14 +53,6 @@ export const overrideServiceConfigForTesting = (config) => {
*
* Exported for testing. See {@link getServiceConfig} for main public accessor.
*
* Config values are resolved by looking for global variables with the names matching the keys of {@link DEFAULT_CONFIG_VALUES}.
*
* If no global value is found for a variable, an error will be thrown if the runtimeEnvironment (ENV variable)
* is set to a "production like" environment.
*
* If {@link allowDefaultConfigValues} returns true for the current environment, the value from {@link DEFAULT_CONFIG_VALUES} will be
* used if a variable is missing.
*
* @returns {ServiceConfiguration}
*/
export function loadServiceConfig() {
Expand All @@ -114,13 +69,14 @@ export function loadServiceConfig() {
* @returns {ServiceConfiguration}
*/
export function serviceConfigFromVariables(vars) {
let clusterUrl = vars.CLUSTER_API_URL
if (vars.CLUSTER_SERVICE) {
const serviceUrl = CLUSTER_SERVICE_URLS[vars.CLUSTER_SERVICE]
if (!serviceUrl) {
let clusterUrl
if (!vars.CLUSTER_SERVICE) {
clusterUrl = vars.CLUSTER_API_URL
} else {
clusterUrl = CLUSTER_SERVICE_URLS[vars.CLUSTER_SERVICE]
if (!clusterUrl) {
throw new Error(`unknown cluster service: ${vars.CLUSTER_SERVICE}`)
}
clusterUrl = serviceUrl
}

return {
Expand All @@ -135,9 +91,6 @@ export function serviceConfigFromVariables(vars) {
CLUSTER_BASIC_AUTH_TOKEN: vars.CLUSTER_BASIC_AUTH_TOKEN,
MAGIC_SECRET_KEY: vars.MAGIC_SECRET_KEY,
SENTRY_DSN: vars.SENTRY_DSN,
NFT_STORAGE_BRANCH: vars.NFT_STORAGE_BRANCH,
NFT_STORAGE_VERSION: vars.NFT_STORAGE_VERSION,
NFT_STORAGE_COMMITHASH: vars.NFT_STORAGE_COMMITHASH,
METAPLEX_AUTH_TOKEN: vars.METAPLEX_AUTH_TOKEN,
MAILCHIMP_API_KEY: vars.MAILCHIMP_API_KEY,
LOGTAIL_TOKEN: vars.LOGTAIL_TOKEN,
Expand All @@ -147,6 +100,13 @@ export function serviceConfigFromVariables(vars) {
S3_SECRET_ACCESS_KEY: vars.S3_SECRET_ACCESS_KEY,
S3_BUCKET_NAME: vars.S3_BUCKET_NAME,
PRIVATE_KEY: vars.PRIVATE_KEY,
// These are injected in esbuild
// @ts-ignore
BRANCH: NFT_STORAGE_BRANCH,
// @ts-ignore
VERSION: NFT_STORAGE_VERSION,
// @ts-ignore
COMMITHASH: NFT_STORAGE_COMMITHASH,
}
}

Expand All @@ -166,29 +126,45 @@ export function loadConfigVariables() {
/** @type Record<string, unknown> */
const globals = globalThis

const notFound = []
for (const name of Object.keys(DEFAULT_CONFIG_VALUES)) {
const required = [
'ENV',
'DEBUG',
'SALT',
'DATABASE_URL',
'DATABASE_TOKEN',
'MAGIC_SECRET_KEY',
'MAILCHIMP_API_KEY',
'METAPLEX_AUTH_TOKEN',
'LOGTAIL_TOKEN',
'PRIVATE_KEY',
'SENTRY_DSN',
'CLUSTER_BASIC_AUTH_TOKEN',
'MAINTENANCE_MODE',
'S3_REGION',
'S3_ACCESS_KEY_ID',
'S3_SECRET_ACCESS_KEY',
'S3_BUCKET_NAME',
]

for (const name of required) {
const val = globals[name]
if (typeof val === 'string') {
vars[name] = val
} else {
notFound.push(name)
}
}

if (notFound.length !== 0) {
const env = parseRuntimeEnv(vars.ENV)
if (!allowDefaultConfigValues(env)) {
throw new Error(
'Missing required config variables: ' + notFound.join(', ')
`Missing required config variables: ${name}. Check your .env, testing globals or cloudflare vars.`
)
}
console.warn(
'Using default values for config variables: ',
notFound.join(', ')
)
for (const name of notFound) {
vars[name] = DEFAULT_CONFIG_VALUES[name]
}

const optional = ['CLUSTER_SERVICE', 'CLUSTER_API_URL', 'S3_ENDPOINT']

for (const name of optional) {
const val = globals[name]
if (typeof val === 'string') {
vars[name] = val
} else {
console.warn(`Missing optional config variables: ${name}`)
}
}

Expand Down
13 changes: 4 additions & 9 deletions packages/api/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,11 @@ r.add(
'get',
'/version',
(event) => {
const {
NFT_STORAGE_VERSION,
NFT_STORAGE_COMMITHASH,
NFT_STORAGE_BRANCH,
MAINTENANCE_MODE,
} = getServiceConfig()
const { VERSION, COMMITHASH, BRANCH, MAINTENANCE_MODE } = getServiceConfig()
return new JSONResponse({
version: NFT_STORAGE_VERSION,
commit: NFT_STORAGE_COMMITHASH,
branch: NFT_STORAGE_BRANCH,
version: VERSION,
commit: COMMITHASH,
branch: BRANCH,
mode: MAINTENANCE_MODE,
})
},
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/utils/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const sentryOptions = {
rewriteFrames: {
root: '/',
},
release: config.NFT_STORAGE_VERSION,
release: config.VERSION,
pkg,
}

Expand Down
9 changes: 4 additions & 5 deletions packages/api/src/utils/logs.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { nanoid } from 'nanoid/non-secure'
import { getServiceConfig } from '../config'

const { NFT_STORAGE_VERSION, NFT_STORAGE_COMMITHASH, NFT_STORAGE_BRANCH } =
getServiceConfig()
const { VERSION, COMMITHASH, BRANCH } = getServiceConfig()
const logtailApiURL = 'https://in.logtail.com/'

const buildMetadataFromHeaders = (/** @type {Headers} */ headers) => {
Expand Down Expand Up @@ -56,9 +55,9 @@ export class Logging {
cf: rCf,
},
cloudflare_worker: {
version: NFT_STORAGE_VERSION,
commit: NFT_STORAGE_COMMITHASH,
branch: NFT_STORAGE_BRANCH,
version: VERSION,
commit: COMMITHASH,
branch: BRANCH,
worker_id: nanoid(10),
worker_started: this.startTs,
},
Expand Down
29 changes: 1 addition & 28 deletions packages/api/test/config.spec.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
import * as assert from 'assert'
import assert from 'assert'
import {
serviceConfigFromVariables,
DEFAULT_CONFIG_VALUES,
loadConfigVariables,
loadServiceConfig,
} from '../src/config.js'

/** @type Record<string, unknown> */
const globals = globalThis

/**
* Helper to remove all the config variables we care about from the global context.
*/
const scrubGlobals = () => {
for (const name of Object.keys(DEFAULT_CONFIG_VALUES)) {
delete globals[name]
}
}

/**
* @param {Record<string, string>} vars
*/
Expand All @@ -28,17 +18,6 @@ const defineGlobals = (vars) => {
}

describe('loadServiceConfig', () => {
beforeEach(scrubGlobals)
afterEach(scrubGlobals)

it('fails if config vars are missing when ENV == "staging" or "production"', () => {
const strictEnvs = ['staging', 'production']
for (const env of strictEnvs) {
defineGlobals({ ENV: env })
assert.throws(() => loadServiceConfig())
}
})

it('uses default config values for missing vars when ENV == "test" or "dev"', () => {
const lenientEnvs = ['test', 'dev']
for (const env of lenientEnvs) {
Expand All @@ -51,9 +30,6 @@ describe('loadServiceConfig', () => {
})

describe('loadConfigVariables', () => {
beforeEach(scrubGlobals)
afterEach(scrubGlobals)

it('looks up values on the globalThis object', () => {
globals['SALT'] = 'extra-salty'
const vars = loadConfigVariables()
Expand Down Expand Up @@ -125,9 +101,6 @@ describe('serviceConfigFromVariables', () => {

describe('uses unaltered values for string config variables', () => {
const stringValuedVars = [
'NFT_STORAGE_VERSION',
'NFT_STORAGE_BRANCH',
'NFT_STORAGE_COMMITHASH',
'SALT',
'METAPLEX_AUTH_TOKEN',
'PRIVATE_KEY',
Expand Down
26 changes: 26 additions & 0 deletions packages/api/test/scripts/globals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
globalThis.ENV = 'test'
globalThis.DEBUG = 'true'
globalThis.SALT = 'secret'
globalThis.DATABASE_URL = 'http://localhost:3000'
globalThis.DATABASE_TOKEN =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTYwMzk2ODgzNCwiZXhwIjoyNTUwNjUzNjM0LCJyb2xlIjoic2VydmljZV9yb2xlIn0.necIJaiP7X2T2QjGeV-FhpkizcNTX8HjDDBAxpgQTEI'

globalThis.MAGIC_SECRET_KEY = 'test'
globalThis.MAILCHIMP_API_KEY = ''
globalThis.METAPLEX_AUTH_TOKEN = 'metaplex-test-token'
globalThis.LOGTAIL_TOKEN = ''
globalThis.PRIVATE_KEY = 'xmbtWjE9eYuAxae9G65lQSkw36HV6H+0LSFq2aKqVwY='
globalThis.SENTRY_DSN = 'https://test@test.ingest.sentry.io/0000000'

globalThis.CLUSTER_API_URL = 'http://localhost:9094'
// will be used with we can active auth in cluster base64 of test:test
globalThis.CLUSTER_BASIC_AUTH_TOKEN = 'dGVzdDp0ZXN0'
globalThis.CLUSTER_SERVICE = ''

globalThis.MAINTENANCE_MODE = 'rw'

globalThis.S3_ENDPOINT = 'http://localhost:9095'
globalThis.S3_REGION = 'test'
globalThis.S3_ACCESS_KEY_ID = 'test'
globalThis.S3_SECRET_ACCESS_KEY = 'test'
globalThis.S3_BUCKET_NAME = 'test'
Loading