Skip to content

Commit

Permalink
Otel configuration updated to use expansion
Browse files Browse the repository at this point in the history
File and env access in configuration now use the generic expansion mechanism introduced in [#1759](#1759.

```yaml
      grpc:
        key:
          file: "foo.txt"
        ca:
          file: "bar.txt"
        cert:
          file: "baz.txt"
```

Becomes:
```yaml
      grpc:
        key: "${file.foo.txt}"
        ca: "${file.bar.txt}"
        cert: "${file.baz.txt}"
```
or
```yaml
      grpc:
        key: "${env.FOO}"
        ca: "${env.BAR}"
        cert: "${env.BAZ}"
```
  • Loading branch information
bryn committed Sep 14, 2022
1 parent 8e68aca commit 279bbd6
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 245 deletions.
32 changes: 31 additions & 1 deletion NEXT_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ By [@garypen](https://github.com/garypen) in https://github.com/apollographql/ro

### Environment variable expansion enhancements ([#1759](https://github.com/apollographql/router/issues/1759))

* Environment expansions must be prefixed with `env.`. This will allow us to add other expansion types in future.
* Environment expansions must be prefixed with `env.`.
* File expansions must be prefixed with `file.`.
* Change defaulting token from `:` to `:-`. For example:

`${env.USER_NAME:Nandor}` => `${env.USER_NAME:-Nandor}`
Expand All @@ -139,6 +140,35 @@ OpenTelemetry attributes should be grouped by `.` rather than `_`, therefore the

By [@BrynCooke](https://github.com/BrynCooke) in https://github.com/apollographql/router/pull/1514

### Otel configuration updated to use expansion ([#1772](https://github.com/apollographql/router/issues/1772))

File and env access in configuration now use the generic expansion mechanism introduced in [#1759](https://github.com/apollographql/router/issues/1759.

```yaml
grpc:
key:
file: "foo.txt"
ca:
file: "bar.txt"
cert:
file: "baz.txt"
```

Becomes:
```yaml
grpc:
key: "${file.foo.txt}"
ca: "${file.bar.txt}"
cert: "${file.baz.txt}"
```
or
```yaml
grpc:
key: "${env.FOO}"
ca: "${env.BAR}"
cert: "${env.BAZ}"
```

## 🚀 Features

### Add support of query resolution with single `__typename` field ([Issue #1761](https://github.com/apollographql/router/issues/1761))
Expand Down
125 changes: 96 additions & 29 deletions apollo-router/src/configuration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ mod yaml;
use std::borrow::Cow;
use std::cmp::Ordering;
use std::env;
use std::env::VarError;
use std::fmt;
use std::fs;
use std::net::IpAddr;
use std::net::SocketAddr;
use std::str::FromStr;
Expand Down Expand Up @@ -36,50 +38,88 @@ use tower_http::cors::{self};

use crate::plugin::plugins;

enum Expansion {
Regular,
#[derive(buildstructor::Builder)]
struct Expansion {
prefix: Option<String>,
supported_modes: Vec<String>,
}

// APOLLO_ROUTER_CONFIG_ENV_PREFIX is undocumented and unsupported currently. If there is demand from the community then it can be promoted to a top level feature.
Prefixed(String),
impl Expansion {
fn default() -> Result<Self, ConfigurationError> {
let prefix = match env::var("APOLLO_ROUTER_CONFIG_ENV_PREFIX") {
Ok(v) => Some(v),
Err(VarError::NotPresent) => None,
Err(VarError::NotUnicode(_)) => Err(ConfigurationError::InvalidExpansionModeConfig)?,
};
let supported_expansion_modes = match env::var("APOLLO_ROUTER_CONFIG_SUPPORTED_MODES") {
Ok(v) => v,
Err(VarError::NotPresent) => "env,file".to_string(),
Err(VarError::NotUnicode(_)) => Err(ConfigurationError::InvalidExpansionModeConfig)?,
};
let supported_modes = supported_expansion_modes
.split(',')
.map(|mode| mode.trim().to_string())
.collect::<Vec<String>>();
Ok(Expansion {
prefix,
supported_modes,
})
}
}

impl Expansion {
fn context_fn(&self) -> impl Fn(&str) -> Result<Option<String>, ConfigurationError> + '_ {
move |key: &str| {
if !self
.supported_modes
.iter()
.any(|prefix| key.starts_with(prefix.as_str()))
{
return Err(ConfigurationError::UnknownExpansionMode {
key: key.to_string(),
supported_modes: self.supported_modes.join("|"),
});
}

if let Some(key) = key.strip_prefix("env.") {
return match self {
Expansion::Regular => env::var(key),
Expansion::Prefixed(prefix) => env::var(format!("{}_{}", prefix, key)),
return match self.prefix.as_ref() {
None => env::var(key),
Some(prefix) => env::var(format!("{}_{}", prefix, key)),
}
.map(Some)
.map_err(|cause| ConfigurationError::CannotExpandVariable {
key: key.to_string(),
cause,
cause: format!("{}", cause),
});
}
Err(ConfigurationError::UnknownExpansionMode {
key: key.to_string(),
})
if let Some(key) = key.strip_prefix("file.") {
if !std::path::Path::new(key).exists() {
return Ok(None);
}

return fs::read_to_string(key).map(Some).map_err(|cause| {
ConfigurationError::CannotExpandVariable {
key: key.to_string(),
cause: format!("{}", cause),
}
});
}
Err(ConfigurationError::InvalidExpansionModeConfig)
}
}
}

/// Configuration error.
#[derive(Debug, Error, Display)]
#[allow(missing_docs)] // FIXME
#[non_exhaustive]
pub(crate) enum ConfigurationError {
/// could not read secret from file: {0}
CannotReadSecretFromFile(std::io::Error),
/// could not read secret from environment variable: {0}
CannotReadSecretFromEnv(std::env::VarError),
/// could not expand variable: {key}, {cause}
CannotExpandVariable {
CannotExpandVariable { key: String, cause: String },
/// could not expand variable: {key}. Variables must be prefixed with one of '{supported_modes}' followed by '.' e.g. 'env.'
UnknownExpansionMode {
key: String,
cause: std::env::VarError,
supported_modes: String,
},
/// could not expand variable: {key}. Variables must be prefixed with `env.`.
UnknownExpansionMode { key: String },
/// unknown plugin {0}
PluginUnknown(String),
/// plugin {plugin} could not be configured: {error}
Expand All @@ -91,6 +131,9 @@ pub(crate) enum ConfigurationError {
},
/// could not deserialize configuration: {0}
DeserializeConfigError(serde_json::Error),

/// APOLLO_ROUTER_CONFIG_SUPPORTED_MODES must be of the format env,file,... Possible modes are 'env' and 'file'.
InvalidExpansionModeConfig,
}

/// The configuration for the router.
Expand Down Expand Up @@ -901,13 +944,7 @@ pub(crate) fn generate_config_schema() -> RootSchema {
/// There may still be serde validation issues later.
///
pub(crate) fn validate_configuration(raw_yaml: &str) -> Result<Configuration, ConfigurationError> {
// APOLLO_ROUTER_CONFIG_ENV_PREFIX is undocumented and unsupported currently. If there is demand from the community then it can be promoted to a top level feature.
let expansion = if let Ok(prefix) = env::var("APOLLO_ROUTER_CONFIG_ENV_PREFIX") {
Expansion::Prefixed(prefix)
} else {
Expansion::Regular
};
validate_configuration_internal(raw_yaml, expansion)
validate_configuration_internal(raw_yaml, Expansion::default()?)
}

fn validate_configuration_internal(
Expand Down Expand Up @@ -1178,6 +1215,7 @@ fn coerce(expanded: &str) -> Value {
mod tests {
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;

use http::Uri;
#[cfg(unix)]
Expand Down Expand Up @@ -1657,11 +1695,15 @@ supergraph:

#[test]
fn expansion_failure_unknown_mode() {
let error = validate_configuration(
let error = validate_configuration_internal(
r#"
supergraph:
introspection: ${unknown.TEST_CONFIG_UNKNOWN_WITH_NO_DEFAULT}
"#,
Expansion::builder()
.prefix("TEST_CONFIG")
.supported_mode("env")
.build(),
)
.expect_err("must have an error because the mode is unknown");
insta::assert_snapshot!(error.to_string());
Expand All @@ -1675,8 +1717,33 @@ supergraph:
supergraph:
introspection: ${env.NEEDS_PREFIX}
"#,
Expansion::Prefixed("TEST_CONFIG".to_string()),
Expansion::builder()
.prefix("TEST_CONFIG")
.supported_mode("env")
.build(),
)
.expect("must have expanded successfully");
}

#[test]
fn expansion_from_file() {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("src");
path.push("configuration");
path.push("testdata");
path.push("true.txt");
let config = validate_configuration_internal(
&format!(
r#"
supergraph:
introspection: ${{file.{}}}
"#,
path.to_string_lossy()
),
Expansion::builder().supported_mode("file").build(),
)
.expect("must have expanded successfully");

assert!(config.supergraph.introspection);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
source: apollo-router/src/configuration/mod.rs
expression: error.to_string()
---
could not expand variable: unknown.TEST_CONFIG_UNKNOWN_WITH_NO_DEFAULT. Variables must be prefixed with `env.`.
could not expand variable: unknown.TEST_CONFIG_UNKNOWN_WITH_NO_DEFAULT. Variables must be prefixed with one of 'env' followed by '.' e.g. 'env.'
Loading

0 comments on commit 279bbd6

Please sign in to comment.