Skip to content

Commit cda053e

Browse files
authored
Merge pull request #10 from duyet/feat/envs
2 parents ef672a6 + ff05eae commit cda053e

File tree

5 files changed

+104
-24
lines changed

5 files changed

+104
-24
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ ascii_table = "3"
2222
md5 = "0.7"
2323
walkdir = "2"
2424
ansi_term = "0.12"
25+
envmnt = "0.9"
2526

2627
[dev-dependencies]
2728
assert_cmd = "0.10"

README.md

+14-12
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ Using `grant` tool:
1616

1717
```bash
1818
$ grant --help
19-
grant 0.0.1-beta.1
19+
20+
grant 0.0.1-beta.2
2021
Manage database roles and privileges in GitOps style
2122

2223
USAGE:
@@ -27,22 +28,21 @@ FLAGS:
2728
-V, --version Prints version information
2829

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

3839
## Generate project structure
3940

4041
```bash
41-
grant gen --target duyet-cluster
42+
grant gen --target ./cluster
4243

43-
# or
44-
mkdir duyet-cluster && cd $_
45-
grant gen --target .
44+
Creating path: "./cluster"
45+
Generated: "./cluster/config.yml"
4646
```
4747

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

5758
roles:
@@ -86,13 +87,13 @@ roles:
8687

8788
users:
8889
- name: duyet
89-
password: 1234567890
90+
password: 1234567890 # password in plaintext
9091
roles:
9192
- role_database_level
9293
- role_all_schema
9394
- role_schema_level
9495
- name: duyet2
95-
password: 1234567890
96+
password: md58243e8f5dfb84bbd851de920e28f596f # support md5 style
9697
roles:
9798
- role_database_level
9899
- role_all_schema
@@ -102,7 +103,7 @@ users:
102103
Apply this config to cluster:
103104
104105
```bash
105-
grant apply --dryrun -f ./examples/example.yaml
106+
grant apply -f ./examples/example.yaml
106107

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

194195
# TODO
195196

197+
- [x] Support reading connection info from environment variables
196198
- [ ] Support store encrypted password in Git
197199
- [x] Support Postgres
198200
- [ ] Visuallization (who can see what?)

examples/example.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
connection:
22
type: "postgres"
3-
url: "postgres://postgres@localhost:5432/postgres"
3+
url: "postgres://postgres:${PASSWORD:postgres}@localhost:5432/postgres"
44

55
roles:
66
- name: role_database_level

src/cli.rs

+11-10
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub struct Cli {
1313

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

36-
/// Apply changes
36+
/// Apply a configuration to a redshift by file name.
37+
/// Yaml format are accepted.
3738
Apply {
3839
/// The path to the file to read
3940
#[structopt(short, long, parse(from_os_str))]
4041
file: PathBuf,
4142

42-
/// Dry run
43+
/// Dry run mode, only print what would be apply
4344
#[structopt(short, long)]
4445
dryrun: bool,
45-
46-
/// Connection string
47-
#[structopt(short, long)]
48-
conn: Option<String>,
4946
},
5047

51-
/// Validate target file
48+
/// Validate a configuration file or
49+
/// a target directory that contains configuration files
5250
Validate {
53-
/// The path to the file to read (optional)
51+
/// The path to the file or directory
52+
/// If the target is not available, the current
53+
/// directory will be used.
5454
#[structopt(short, long, parse(from_os_str))]
5555
file: Option<PathBuf>,
5656
},
5757

58-
/// Inspect current database cluster by config file
58+
/// Inspect current database cluster
59+
/// with connection info from configuration file
5960
Inspect {
6061
/// The path to the file to read
6162
#[structopt(short, long, parse(from_os_str))]

src/config.rs

+77-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use anyhow::{anyhow, Context, Result};
2+
use envmnt::{ExpandOptions, ExpansionType};
23
use serde::{Deserialize, Serialize};
34
use serde_yaml;
45
use std::collections::HashSet;
@@ -30,14 +31,30 @@ pub struct Connection {
3031

3132
impl Connection {
3233
pub fn new(type_: ConnectionType, url: String) -> Self {
33-
Self { type_, url }
34+
let mut conn = Self { type_, url };
35+
conn = conn.expand_env_vars().unwrap();
36+
37+
conn
3438
}
3539

3640
pub fn validate(&self) -> Result<()> {
3741
match self.type_ {
3842
ConnectionType::Postgres => Ok(()),
3943
}
4044
}
45+
46+
// xpaned environtment variables in the `url` field.
47+
// Expand environment variables in the `url` field.
48+
// For example: postgres://user:${PASSWORD}@host:port/database
49+
pub fn expand_env_vars(&self) -> Result<Self> {
50+
let mut connection = self.clone();
51+
52+
let mut options = ExpandOptions::new();
53+
options.expansion_type = Some(ExpansionType::UnixBracketsWithDefaults);
54+
connection.url = envmnt::expand(&self.url, Some(options));
55+
56+
Ok(connection)
57+
}
4158
}
4259

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

532549
config.validate()?;
533550

551+
// expand env variables
552+
let config = config.expand_env_vars()?;
553+
534554
Ok(config)
535555
}
536556

@@ -582,6 +602,16 @@ impl Config {
582602

583603
Ok(())
584604
}
605+
606+
// Expand env variables in config
607+
fn expand_env_vars(&self) -> Result<Self> {
608+
let mut config = self.clone();
609+
610+
// expand connection
611+
config.connection = config.connection.expand_env_vars()?;
612+
613+
Ok(config)
614+
}
585615
}
586616

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

672+
// Test config with url contains environement variable
673+
#[test]
674+
fn test_read_config_with_env_var() {
675+
envmnt::set("POSTGRES_HOST", "duyet");
676+
677+
let _text = indoc! {"
678+
connection:
679+
type: postgres
680+
url: postgres://${POSTGRES_HOST}:5432/postgres
681+
roles: []
682+
users: []
683+
"};
684+
685+
let mut file = NamedTempFile::new().expect("failed to create temp file");
686+
file.write(_text.as_bytes())
687+
.expect("failed to write to temp file");
688+
let path = PathBuf::from(file.path().to_str().unwrap());
689+
690+
let config = Config::new(&path).expect("failed to get content");
691+
692+
assert_eq!(config.connection.url, "postgres://duyet:5432/postgres");
693+
694+
envmnt::remove("POSTGRES_HOST");
695+
}
696+
697+
// Test expand environement variables but not available
698+
#[test]
699+
fn test_read_config_with_env_var_not_available() {
700+
let _text = indoc! {"
701+
connection:
702+
type: postgres
703+
url: postgres://${POSTGRES_HOST:duyet}:5432/${POSTGRES_ABC}
704+
roles: []
705+
users: []
706+
"};
707+
708+
let mut file = NamedTempFile::new().expect("failed to create temp file");
709+
file.write(_text.as_bytes())
710+
.expect("failed to write to temp file");
711+
let path = PathBuf::from(file.path().to_str().unwrap());
712+
713+
let config = Config::new(&path).expect("failed to get content");
714+
715+
assert_eq!(config.connection.url, "postgres://duyet:5432/");
716+
}
717+
642718
// Test config with invalid connection type
643719
#[test]
644720
#[should_panic(expected = "connection.type: unknown variant `invalid`")]

0 commit comments

Comments
 (0)