Skip to content

Commit

Permalink
Merge pull request #10 from duyet/feat/envs
Browse files Browse the repository at this point in the history
  • Loading branch information
duyet authored Dec 7, 2021
2 parents ef672a6 + ff05eae commit cda053e
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 24 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ ascii_table = "3"
md5 = "0.7"
walkdir = "2"
ansi_term = "0.12"
envmnt = "0.9"

[dev-dependencies]
assert_cmd = "0.10"
Expand Down
26 changes: 14 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ Using `grant` tool:

```bash
$ grant --help
grant 0.0.1-beta.1

grant 0.0.1-beta.2
Manage database roles and privileges in GitOps style

USAGE:
Expand All @@ -27,22 +28,21 @@ FLAGS:
-V, --version Prints version information

SUBCOMMANDS:
apply Apply changes
gen Generate project
apply Apply a configuration to a redshift by file name. Yaml format are accepted
gen Generate sample configuration file
gen-pass Generate random password
help Prints this message or the help of the given subcommand(s)
inspect Inspect current database cluster by config file
validate Validate target file
inspect Inspect current database cluster with connection info from configuration file
validate Validate a configuration file or a target directory that contains configuration files
```

## Generate project structure

```bash
grant gen --target duyet-cluster
grant gen --target ./cluster

# or
mkdir duyet-cluster && cd $_
grant gen --target .
Creating path: "./cluster"
Generated: "./cluster/config.yml"
```

## Apply privilege changes
Expand All @@ -52,6 +52,7 @@ Content of `./examples/example.yaml`:
```yaml
connection:
type: "postgres"
# support environment variables, e.g. postgres://${HOSTNAME}:5432
url: "postgres://postgres@localhost:5432/postgres"

roles:
Expand Down Expand Up @@ -86,13 +87,13 @@ roles:

users:
- name: duyet
password: 1234567890
password: 1234567890 # password in plaintext
roles:
- role_database_level
- role_all_schema
- role_schema_level
- name: duyet2
password: 1234567890
password: md58243e8f5dfb84bbd851de920e28f596f # support md5 style
roles:
- role_database_level
- role_all_schema
Expand All @@ -102,7 +103,7 @@ users:
Apply this config to cluster:
```bash
grant apply --dryrun -f ./examples/example.yaml
grant apply -f ./examples/example.yaml

[2021-12-06T14:37:03Z INFO grant::connection] Connected to database: postgres://postgres@localhost:5432/postgres
[2021-12-06T14:37:03Z INFO grant::apply] Summary:
Expand Down Expand Up @@ -193,6 +194,7 @@ cargo test

# TODO

- [x] Support reading connection info from environment variables
- [ ] Support store encrypted password in Git
- [x] Support Postgres
- [ ] Visuallization (who can see what?)
Expand Down
2 changes: 1 addition & 1 deletion examples/example.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
connection:
type: "postgres"
url: "postgres://postgres@localhost:5432/postgres"
url: "postgres://postgres:${PASSWORD:postgres}@localhost:5432/postgres"

roles:
- name: role_database_level
Expand Down
21 changes: 11 additions & 10 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub struct Cli {

#[derive(StructOpt, Debug)]
pub enum Command {
/// Generate project
/// Generate sample configuration file
Gen {
/// The target folder
#[structopt(short, long, default_value = ".", parse(from_os_str))]
Expand All @@ -33,29 +33,30 @@ pub enum Command {
password: Option<String>,
},

/// Apply changes
/// Apply a configuration to a redshift by file name.
/// Yaml format are accepted.
Apply {
/// The path to the file to read
#[structopt(short, long, parse(from_os_str))]
file: PathBuf,

/// Dry run
/// Dry run mode, only print what would be apply
#[structopt(short, long)]
dryrun: bool,

/// Connection string
#[structopt(short, long)]
conn: Option<String>,
},

/// Validate target file
/// Validate a configuration file or
/// a target directory that contains configuration files
Validate {
/// The path to the file to read (optional)
/// The path to the file or directory
/// If the target is not available, the current
/// directory will be used.
#[structopt(short, long, parse(from_os_str))]
file: Option<PathBuf>,
},

/// Inspect current database cluster by config file
/// Inspect current database cluster
/// with connection info from configuration file
Inspect {
/// The path to the file to read
#[structopt(short, long, parse(from_os_str))]
Expand Down
78 changes: 77 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use anyhow::{anyhow, Context, Result};
use envmnt::{ExpandOptions, ExpansionType};
use serde::{Deserialize, Serialize};
use serde_yaml;
use std::collections::HashSet;
Expand Down Expand Up @@ -30,14 +31,30 @@ pub struct Connection {

impl Connection {
pub fn new(type_: ConnectionType, url: String) -> Self {
Self { type_, url }
let mut conn = Self { type_, url };
conn = conn.expand_env_vars().unwrap();

conn
}

pub fn validate(&self) -> Result<()> {
match self.type_ {
ConnectionType::Postgres => Ok(()),
}
}

// xpaned environtment variables in the `url` field.
// Expand environment variables in the `url` field.
// For example: postgres://user:${PASSWORD}@host:port/database
pub fn expand_env_vars(&self) -> Result<Self> {
let mut connection = self.clone();

let mut options = ExpandOptions::new();
options.expansion_type = Some(ExpansionType::UnixBracketsWithDefaults);
connection.url = envmnt::expand(&self.url, Some(options));

Ok(connection)
}
}

// Implement default values for connection type and url.
Expand Down Expand Up @@ -531,6 +548,9 @@ impl Config {

config.validate()?;

// expand env variables
let config = config.expand_env_vars()?;

Ok(config)
}

Expand Down Expand Up @@ -582,6 +602,16 @@ impl Config {

Ok(())
}

// Expand env variables in config
fn expand_env_vars(&self) -> Result<Self> {
let mut config = self.clone();

// expand connection
config.connection = config.connection.expand_env_vars()?;

Ok(config)
}
}

impl fmt::Display for Config {
Expand Down Expand Up @@ -639,6 +669,52 @@ mod tests {
Config::new(&path).expect("failed to get content");
}

// Test config with url contains environement variable
#[test]
fn test_read_config_with_env_var() {
envmnt::set("POSTGRES_HOST", "duyet");

let _text = indoc! {"
connection:
type: postgres
url: postgres://${POSTGRES_HOST}:5432/postgres
roles: []
users: []
"};

let mut file = NamedTempFile::new().expect("failed to create temp file");
file.write(_text.as_bytes())
.expect("failed to write to temp file");
let path = PathBuf::from(file.path().to_str().unwrap());

let config = Config::new(&path).expect("failed to get content");

assert_eq!(config.connection.url, "postgres://duyet:5432/postgres");

envmnt::remove("POSTGRES_HOST");
}

// Test expand environement variables but not available
#[test]
fn test_read_config_with_env_var_not_available() {
let _text = indoc! {"
connection:
type: postgres
url: postgres://${POSTGRES_HOST:duyet}:5432/${POSTGRES_ABC}
roles: []
users: []
"};

let mut file = NamedTempFile::new().expect("failed to create temp file");
file.write(_text.as_bytes())
.expect("failed to write to temp file");
let path = PathBuf::from(file.path().to_str().unwrap());

let config = Config::new(&path).expect("failed to get content");

assert_eq!(config.connection.url, "postgres://duyet:5432/");
}

// Test config with invalid connection type
#[test]
#[should_panic(expected = "connection.type: unknown variant `invalid`")]
Expand Down

0 comments on commit cda053e

Please sign in to comment.