Skip to content

Commit

Permalink
Add support for floki to interpret host environmental variables in fl…
Browse files Browse the repository at this point in the history
…oki.yaml
  • Loading branch information
peter-oneill committed Jul 4, 2023
1 parent 861f462 commit dae0938
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Status: Available for use
## [Unreleased]

### Breaking Changes
- Allow templating using environmental variables in `floki.yaml`, with `${user_env:VAR}` replaced with value of the user's environmental variable `VAR` before deserialization.

### Added

Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ shlex = "1.1"
sha2 = "0.10.7"
anyhow = "1.0.71"
thiserror = "1.0.40"
regex = "1"

[dev-dependencies]
tempfile = "3.6.0"
15 changes: 15 additions & 0 deletions docs/content/intro/feature-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,18 @@ Note that use of `docker_switches` may reduce the reproducibility and shareabili

Nonetheless, it is useful to be able to add arbitrary switches in a pinch, just to be able to get something working.
If there are things you can add with `docker_switches` which are reproducible and shareable, please raise a feature request, or go ahead and implement it yourself!

# Using environmental variables in yaml

Sometimes it is helpful for the `floki` config to include environment-specific values. For example, mounting a subdirectory of the user's home directory. `floki` supports this via templating. Specify environmental variables in `floki.yaml` as `${user_env:<ENV>}`. Before interpreting the config, `floki` will substitute the user's environmental variables for those values.

```yaml
docker_switches:
- -v ${user_env:HOME}/.vim:/home/build/.vim
```

Only alphanumeric and underscores (`[a-zA-Z0-9_]`) are supported in environmental variable names. Inclusion of other characters afer `user_env` will prevent the substitution from taking place.

Warning:
- Use of host environmental variables may reduce the reproducibility and shareability of your `floki.yaml`.
- If an environmental variable is not found, it is interpreted as a blank string.
52 changes: 47 additions & 5 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
use crate::errors;
use crate::image;
use anyhow::Error;
use regex::{Captures, Regex};
use serde::{Deserialize, Serialize};

use std::collections::BTreeMap;
use std::fs::File;
use std::io::Read;
use std::path;

#[derive(Debug, PartialEq, Serialize, Deserialize)]
Expand Down Expand Up @@ -100,12 +102,22 @@ impl FlokiConfig {
pub fn from_file(file: &path::Path) -> Result<FlokiConfig, Error> {
debug!("Reading configuration file: {:?}", file);

let f = File::open(file).map_err(|e| errors::FlokiError::ProblemOpeningConfigYaml {
name: file.display().to_string(),
error: e,
})?;
let mut contents = String::new();

let mut config: FlokiConfig = serde_yaml::from_reader(f).map_err(|e| {
File::open(file)
.map_err(|e| errors::FlokiError::ProblemOpeningConfigYaml {
name: file.display().to_string(),
error: e,
})?
.read_to_string(&mut contents)
.map_err(|e| errors::FlokiError::ProblemReadingConfigYaml {
name: file.display().to_string(),
error: e,
})?;

contents = Self::replace_user_env(&contents);

let mut config: FlokiConfig = serde_yaml::from_str(contents.as_str()).map_err(|e| {
errors::FlokiError::ProblemParsingConfigYaml {
name: file.display().to_string(),
error: e,
Expand Down Expand Up @@ -139,6 +151,17 @@ impl FlokiConfig {

Ok(config)
}

/// Replace all instances of ${user_env:VAR} in the yaml with the value of the local environmental variable "VAR".
fn replace_user_env(input_string: &str) -> String {
let user_env_regex = Regex::new(r"\$\{user_env:([a-zA-Z0-9_]*)\}").unwrap();
user_env_regex
.replace_all(input_string, |caps: &Captures| {
let env_var_name = caps.get(1).unwrap().as_str();
std::env::var(env_var_name).unwrap_or_default()
})
.to_string()
}
}

fn default_shell() -> Shell {
Expand All @@ -160,6 +183,25 @@ fn default_entrypoint() -> Entrypoint {
#[cfg(test)]
mod test {
use super::*;
use image::Image;

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct TestImageConfig {
image: Image,
}

#[test]
fn test_user_env_config() {
std::env::set_var("image", "a_local_user_variable");
let content = "image: prefix_${user_env:image}:1.1";

let expected = TestImageConfig {
image: Image::Name("prefix_a_local_user_variable:1.1".into()),
};
let actual: TestImageConfig =
serde_yaml::from_str(&FlokiConfig::replace_user_env(content)).unwrap();
assert!(actual == expected);
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct TestShellConfig {
Expand Down
3 changes: 3 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ pub enum FlokiError {
#[error("There was a problem opening the configuration file '{name}': {error:?}")]
ProblemOpeningConfigYaml { name: String, error: io::Error },

#[error("There was a problem reading the configuration file '{name}': {error:?}")]
ProblemReadingConfigYaml { name: String, error: io::Error },

#[error("There was a problem parsing the configuration file '{name}': {error:?}")]
ProblemParsingConfigYaml {
name: String,
Expand Down

0 comments on commit dae0938

Please sign in to comment.