Skip to content

Commit 355aa42

Browse files
authored
Read the connection URI from the environment. (#325)
### What The connection URI can now be provided from the environment, using `"uri": {"variable": "..."}`. This means that we can use the same configuration in development and production, and enables the CLI or Cloud build infrastructure to fill it in with e.g. a region-specific URL. ### How We replace the `ResolvedSecret` from the NDC SDK (which has also been removed from the SDK) with our own `Secret` type, which can be `Plain` or `FromEnvironment`. We then handle the case appropriately, whereas before, we would panic if we ever received a non-resolved secret. I have introduced the `Environment` trait to supply environment variables. During normal execution, this is a wrapper around `sys::env::var`. In tests, however, we can use `EmptyEnvironment`, or a `HashMap` containing test data. This also simplified a couple of pieces of test infrastructure. * We no longer need to template the Aurora configuration (we just provide the appropriate value as an environment variable). * Generating configuration becomes a single call to `ndc-postgres-cli update`. * The benchmarks no longer need a generated configuration file.
1 parent d1c97ea commit 355aa42

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+461
-402
lines changed

.github/workflows/benchmarks.yaml

-10
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,6 @@ jobs:
3434
cd benchmarks/component
3535
docker compose up --detach --wait postgres grafana
3636
37-
- name: Generate the NDC metadata configuration 🚧
38-
run: |
39-
set -e -u -o pipefail
40-
cd benchmarks/component
41-
mkdir -p generated/ndc-metadata
42-
jq --arg uri 'postgresql://postgres:password@postgres' \
43-
'.connectionUri.uri.value = $uri' \
44-
../../static/postgres/v3-chinook-ndc-metadata/configuration.json \
45-
> ./generated/ndc-metadata/configuration.json
46-
4737
- name: Run benchmarks 🏃
4838
run: |
4939
cd benchmarks/component

.github/workflows/cargo-test.yaml

-9
Original file line numberDiff line numberDiff line change
@@ -122,15 +122,6 @@ jobs:
122122
version: "25.x"
123123
repo-token: ${{ secrets.GITHUB_TOKEN }}
124124

125-
- name: setup NDC metadata
126-
env:
127-
AURORA_CONNECTION_STRING: ${{ secrets.AURORA_CONNECTION_STRING }}
128-
run: |
129-
jq \
130-
'.connectionUri = {"uri": {"value": (env | .AURORA_CONNECTION_STRING)}}' \
131-
static/aurora/v3-chinook-ndc-metadata/template.json \
132-
> static/aurora/v3-chinook-ndc-metadata/configuration.json
133-
134125
- name: install tools
135126
run: |
136127
rustup show

Cargo.lock

+2-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

benchmarks/component/docker-compose.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,12 @@ services:
5454
ports:
5555
- 8080:8080
5656
environment:
57-
CONFIGURATION_FILE: "/ndc-metadata.json"
57+
CONNECTION_URI: "postgresql://postgres:password@postgres"
5858
# we don't care about traces right now, and the benchmarks flood the batch buffer
5959
OTEL_TRACES_SAMPLER: "always_off"
6060
volumes:
6161
- type: bind
62-
source: ./generated/ndc-metadata
62+
source: ../../static/postgres/v3-chinook-ndc-metadata
6363
target: /etc/connector
6464
read_only: true
6565
healthcheck:

benchmarks/component/start.sh

+3-9
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,17 @@ info 'Starting the dependencies'
2626
docker compose up --wait postgres grafana
2727
POSTGRESQL_SOCKET="$(docker compose port postgres 5432)"
2828

29-
info 'Generating the NDC metadata configuration'
30-
mkdir -p generated/ndc-metadata
31-
jq --arg uri "postgresql://postgres:password@${POSTGRESQL_SOCKET}" \
32-
'.connectionUri.uri.value = $uri' \
33-
../../static/postgres/v3-chinook-ndc-metadata/configuration.json \
34-
> ./generated/ndc-metadata/configuration.json
35-
3629
info 'Starting the agent'
3730
if nc -z localhost 8080; then
3831
echo >&2 'ERROR: There is already an agent running on port 8080.'
3932
exit 1
4033
fi
4134

35+
CONNECTION_URI="postgresql://postgres:password@${POSTGRESQL_SOCKET}" \
4236
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT='http://localhost:4317' \
43-
OTEL_SERVICE_NAME='ndc-postgres' \
37+
OTEL_SERVICE_NAME='ndc-postgres' \
4438
cargo run -p ndc-postgres --quiet --release -- \
45-
serve --configuration=./generated/ndc-metadata \
39+
serve --configuration='../../static/postgres/v3-chinook-ndc-metadata' \
4640
>& agent.log &
4741
AGENT_PID=$!
4842
echo "$AGENT_PID" > ./agent.pid

crates/cli/src/lib.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,14 @@ pub enum Error {
2727
}
2828

2929
/// Run a command in a given directory.
30-
pub async fn run(command: Command, context_path: &Path) -> anyhow::Result<()> {
30+
pub async fn run(
31+
command: Command,
32+
context_path: &Path,
33+
environment: impl configuration::environment::Environment,
34+
) -> anyhow::Result<()> {
3135
match command {
3236
Command::Initialize => initialize(context_path)?,
33-
Command::Update => update(context_path).await?,
37+
Command::Update => update(context_path, environment).await?,
3438
};
3539
Ok(())
3640
}
@@ -58,13 +62,16 @@ fn initialize(context_path: &Path) -> anyhow::Result<()> {
5862
/// Update the configuration in the current directory by introspecting the database.
5963
///
6064
/// This expects a configuration with a valid connection URI.
61-
async fn update(context_path: &Path) -> anyhow::Result<()> {
65+
async fn update(
66+
context_path: &Path,
67+
environment: impl configuration::environment::Environment,
68+
) -> anyhow::Result<()> {
6269
let configuration_file_path = context_path.join(configuration::CONFIGURATION_FILENAME);
6370
let input: configuration::RawConfiguration = {
6471
let reader = fs::File::open(&configuration_file_path)?;
6572
serde_json::from_reader(reader)?
6673
};
67-
let output = configuration::introspect(input).await?;
74+
let output = configuration::introspect(input, environment).await?;
6875
let writer = fs::File::create(&configuration_file_path)?;
6976
serde_json::to_writer_pretty(writer, &output)?;
7077
Ok(())

crates/cli/src/main.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::path::PathBuf;
99
use clap::Parser;
1010

1111
use ndc_postgres_cli::*;
12+
use ndc_postgres_configuration as configuration;
1213

1314
/// The command-line arguments.
1415
#[derive(Debug, Parser)]
@@ -31,6 +32,11 @@ pub async fn main() -> anyhow::Result<()> {
3132
Some(path) => path,
3233
None => env::current_dir()?,
3334
};
34-
run(args.subcommand, &context_path).await?;
35+
run(
36+
args.subcommand,
37+
&context_path,
38+
configuration::environment::ProcessEnvironment,
39+
)
40+
.await?;
3541
Ok(())
3642
}

crates/cli/tests/initialize_tests.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
use std::fs;
22

33
use ndc_postgres_cli::*;
4+
use ndc_postgres_configuration as configuration;
45
use ndc_postgres_configuration::RawConfiguration;
56

67
#[tokio::test]
78
async fn test_initialize_directory() -> anyhow::Result<()> {
89
let dir = tempfile::tempdir()?;
910

10-
run(Command::Initialize, dir.path()).await?;
11+
run(
12+
Command::Initialize,
13+
dir.path(),
14+
configuration::environment::EmptyEnvironment,
15+
)
16+
.await?;
1117

1218
let configuration_file_path = dir.path().join("configuration.json");
1319
assert!(configuration_file_path.exists());
@@ -25,7 +31,13 @@ async fn test_do_not_initialize_when_files_already_exist() -> anyhow::Result<()>
2531
"this directory is no longer empty",
2632
)?;
2733

28-
match run(Command::Initialize, dir.path()).await {
34+
match run(
35+
Command::Initialize,
36+
dir.path(),
37+
configuration::environment::EmptyEnvironment,
38+
)
39+
.await
40+
{
2941
Ok(()) => panic!("Expected the command to fail."),
3042
Err(error) => match error.downcast::<Error>() {
3143
Err(input) => panic!("Expected a CLI error, but got {input}"),

crates/cli/tests/update_tests.rs

+12-6
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,44 @@
1+
use std::collections::HashMap;
12
use std::fs;
23

34
use ndc_postgres_cli::*;
45
use ndc_postgres_configuration as configuration;
56
use ndc_postgres_configuration::RawConfiguration;
67

7-
const CONNECTION_STRING: &str = "postgresql://postgres:password@localhost:64002";
8+
const CONNECTION_URI: &str = "postgresql://postgres:password@localhost:64002";
89

910
#[tokio::test]
1011
async fn test_update_configuration() -> anyhow::Result<()> {
1112
let dir = tempfile::tempdir()?;
13+
14+
let connection_uri = configuration::ConnectionUri(configuration::Secret::FromEnvironment {
15+
variable: "CONNECTION_URI".into(),
16+
});
17+
1218
{
1319
let configuration_file_path = dir.path().join("configuration.json");
1420
let input = RawConfiguration::Version3(configuration::version3::RawConfiguration {
15-
connection_uri: CONNECTION_STRING.into(),
21+
connection_uri: connection_uri.clone(),
1622
..configuration::version3::RawConfiguration::empty()
1723
});
1824
let writer = fs::File::create(configuration_file_path)?;
1925
serde_json::to_writer(writer, &input)?;
2026
}
2127

22-
run(Command::Update, dir.path()).await?;
28+
let environment = HashMap::from([("CONNECTION_URI".into(), CONNECTION_URI.to_string())]);
29+
run(Command::Update, dir.path(), environment).await?;
2330

2431
let configuration_file_path = dir.path().join("configuration.json");
2532
assert!(configuration_file_path.exists());
2633
let contents = fs::read_to_string(configuration_file_path)?;
2734
let output: RawConfiguration = serde_json::from_str(&contents)?;
2835
match output {
2936
RawConfiguration::Version3(configuration::version3::RawConfiguration {
30-
connection_uri:
31-
configuration::ConnectionUri::Uri(configuration::ResolvedSecret(connection_uri)),
37+
connection_uri: updated_connection_uri,
3238
metadata,
3339
..
3440
}) => {
35-
assert_eq!(connection_uri, CONNECTION_STRING.to_string());
41+
assert_eq!(updated_connection_uri, connection_uri);
3642
let some_table_metadata = metadata.tables.0.get("Artist");
3743
assert!(some_table_metadata.is_some());
3844
}

crates/configuration/Cargo.toml

+3-2
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ workspace = true
99
[dependencies]
1010
query-engine-metadata = { path = "../query-engine/metadata" }
1111

12-
ndc-sdk = { git = "https://github.com/hasura/ndc-hub.git", rev = "09edbd5" }
12+
ndc-sdk = { git = "https://github.com/hasura/ndc-hub.git", rev = "393213d" }
1313

14+
anyhow = "1.0.80"
1415
schemars = { version = "0.8.16", features = ["smol_str", "preserve_order"] }
1516
serde = "1.0.197"
1617
serde_json = { version = "1.0.114", features = ["raw_value"] }
1718
sqlx = { version = "0.7.3", features = [ "json", "postgres", "runtime-tokio-rustls" ] }
19+
thiserror = "1.0.57"
1820
tracing = "0.1.40"
19-
anyhow = "1.0.80"

crates/configuration/src/configuration.rs

+24-7
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ use ndc_sdk::connector;
1010

1111
use query_engine_metadata::metadata;
1212

13-
use crate::values::{ConnectionUri, IsolationLevel, PoolSettings, ResolvedSecret};
13+
use crate::environment::Environment;
14+
use crate::values::{ConnectionUri, IsolationLevel, PoolSettings, Secret};
1415
use crate::version3;
1516

1617
pub const CONFIGURATION_FILENAME: &str = "configuration.json";
@@ -43,34 +44,50 @@ pub struct Configuration {
4344
pub mutations_version: Option<metadata::mutations::MutationsVersion>,
4445
}
4546

46-
pub async fn introspect(input: RawConfiguration) -> anyhow::Result<RawConfiguration> {
47+
pub async fn introspect(
48+
input: RawConfiguration,
49+
environment: impl Environment,
50+
) -> anyhow::Result<RawConfiguration> {
4751
match input {
4852
RawConfiguration::Version3(config) => Ok(RawConfiguration::Version3(
49-
version3::introspect(config).await?,
53+
version3::introspect(config, environment).await?,
5054
)),
5155
}
5256
}
5357

5458
pub async fn parse_configuration(
5559
configuration_dir: impl AsRef<Path>,
60+
environment: impl Environment,
5661
) -> Result<Configuration, connector::ParseError> {
5762
let configuration_file = configuration_dir.as_ref().join(CONFIGURATION_FILENAME);
5863
let configuration_reader = fs::File::open(&configuration_file)?;
5964
let configuration: version3::RawConfiguration = serde_json::from_reader(configuration_reader)
6065
.map_err(|error| {
6166
connector::ParseError::ParseError(connector::LocatedError {
62-
file_path: configuration_file,
67+
file_path: configuration_file.clone(),
6368
line: error.line(),
6469
column: error.column(),
6570
message: error.to_string(),
6671
})
6772
})?;
73+
let connection_uri = match configuration.connection_uri {
74+
ConnectionUri(Secret::Plain(uri)) => Ok(uri),
75+
ConnectionUri(Secret::FromEnvironment { variable }) => {
76+
environment.read(&variable).map_err(|error| {
77+
connector::ParseError::ValidateError(connector::InvalidNodes(vec![
78+
connector::InvalidNode {
79+
file_path: configuration_file,
80+
node_path: vec![connector::KeyOrIndex::Key("connectionUri".to_string())],
81+
message: error.to_string(),
82+
},
83+
]))
84+
})
85+
}
86+
}?;
6887
Ok(Configuration {
6988
metadata: configuration.metadata,
7089
pool_settings: configuration.pool_settings,
71-
connection_uri: match configuration.connection_uri {
72-
ConnectionUri::Uri(ResolvedSecret(uri)) => uri,
73-
},
90+
connection_uri,
7491
isolation_level: configuration.isolation_level,
7592
mutations_version: configuration.configure_options.mutations_version,
7693
})

0 commit comments

Comments
 (0)