Skip to content

Commit

Permalink
Setup from env var (#103)
Browse files Browse the repository at this point in the history
  • Loading branch information
c410-f3r authored May 27, 2020
1 parent 0e3559a commit 947dd26
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 16 deletions.
6 changes: 6 additions & 0 deletions refinery_cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ pub fn create_cli() -> App<'static, 'static> {
.help("give a config file location")
.default_value("./refinery.toml"),
)
.arg(
Arg::with_name("env-var")
.short("e")
.help("if specified, loads database configuration from the given environment variable")
.takes_value(true),
)
.arg(
Arg::with_name("grouped")
.short("g")
Expand Down
30 changes: 22 additions & 8 deletions refinery_cli/src/migrate.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
use std::path::Path;

use anyhow::{Context, Result};
use anyhow::Context;
use clap::ArgMatches;
use refinery_core::{config::Config, find_migration_files, Migration, MigrationType, Runner};

pub fn handle_migration_command(args: &ArgMatches) -> Result<()> {
pub fn handle_migration_command(args: &ArgMatches) -> anyhow::Result<()> {
//safe to call unwrap as we specified default values
let config_location = args.value_of("config").unwrap();
let grouped = args.is_present("grouped");
let divergent = !args.is_present("divergent");
let missing = !args.is_present("missing");
let env_var_opt = args.value_of("env-var");

match args.subcommand() {
("files", Some(args)) => {
run_files_migrations(config_location, grouped, divergent, missing, args)?
}
("files", Some(args)) => run_files_migrations(
config_location,
grouped,
divergent,
missing,
env_var_opt,
args,
)?,
_ => unreachable!("Can't touch this..."),
}
Ok(())
Expand All @@ -25,8 +31,9 @@ fn run_files_migrations(
grouped: bool,
divergent: bool,
missing: bool,
env_var_opt: Option<&str>,
arg: &ArgMatches,
) -> Result<()> {
) -> anyhow::Result<()> {
//safe to call unwrap as we specified default value
let path = arg.value_of("path").unwrap();
let path = Path::new(path);
Expand All @@ -46,12 +53,19 @@ fn run_files_migrations(
.with_context(|| format!("could not read migration file name {}", path.display()))?;
migrations.push(migration);
}
let mut config =
Config::from_file_location(config_location).context("could not parse the config file")?;
let mut config = config(config_location, env_var_opt)?;
Runner::new(&migrations)
.set_grouped(grouped)
.set_abort_divergent(divergent)
.set_abort_missing(missing)
.run(&mut config)?;
Ok(())
}

fn config(config_location: &str, env_var_opt: Option<&str>) -> anyhow::Result<Config> {
if let Some(env_var) = env_var_opt {
Config::from_env_var(env_var).context("could not environemnt variable")
} else {
Config::from_file_location(config_location).context("could not parse the config file")
}
}
11 changes: 6 additions & 5 deletions refinery_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@ default = []
rusqlite-bundled = ["rusqlite", "rusqlite/bundled"]

[dependencies]
async-trait = "0.1"
cfg-if = "0.1.10"
chrono = "0.4"
lazy_static = "1"
regex = "1"
log = "0.4"
chrono = "0.4"
regex = "1"
serde = { version = "1", features = ["derive"] }
cfg-if = "0.1.10"
siphasher = "0.3"
thiserror = "1"
async-trait = "0.1"
toml = "0.5"
siphasher = "0.3"
url = "2.0"
walkdir = "2.3.1"

rusqlite = {version = "0.23", optional = true}
Expand Down
67 changes: 65 additions & 2 deletions refinery_core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,50 @@ impl Config {
}
}

/// create a new Config instance from an environment variable that contains an URL
pub fn from_env_var(name: &str) -> Result<Config, Error> {
let value = std::env::var(name).map_err(|_| {
Error::new(
Kind::ConfigError(format!("Couldn't find {} environemnt variable", name)),
None,
)
})?;
let url = url::Url::parse(&value).map_err(|_| {
Error::new(
Kind::ConfigError(format!("Couldn't parse the contents of {} as an URL", name)),
None,
)
})?;
let db_type = match url.scheme() {
"mysql" => ConfigDbType::Mysql,
"postgres" => ConfigDbType::Postgres,
"sqlite" => ConfigDbType::Sqlite,
_ => {
return Err(Error::new(
Kind::ConfigError(format!("Unsupported database")),
None,
))
}
};
Ok(Self {
main: Main {
db_type,
db_path: Some(
url.as_str()[url.scheme().len()..]
.trim_start_matches(':')
.trim_start_matches("//")
.to_string()
.into(),
),
db_host: url.host_str().map(|r| r.to_string()),
db_port: url.port().map(|r| r.to_string()),
db_user: Some(url.username().to_string()),
db_pass: url.password().map(|r| r.to_string()),
db_name: Some(url.path().trim_start_matches('/').to_string()),
},
})
}

/// create a new Config instance from a config file located on the file system
pub fn from_file_location<T: AsRef<Path>>(location: T) -> Result<Config, Error> {
let file = std::fs::read_to_string(&location).map_err(|err| {
Expand Down Expand Up @@ -193,9 +237,8 @@ pub(crate) fn build_db_url(name: &str, config: &Config) -> String {

#[cfg(test)]
mod tests {
use super::{build_db_url, Config, Error, Kind};
use super::{build_db_url, Config, Kind};
use std::io::Write;
use std::path::Path;

#[test]
fn returns_config_error_from_invalid_config_location() {
Expand Down Expand Up @@ -276,4 +319,24 @@ mod tests {
build_db_url("postgres", &config)
);
}

#[test]
fn builds_db_env_var() {
std::env::set_var(
"DATABASE_URL",
"postgres://root:1234@localhost:5432/refinery",
);
let config = Config::from_env_var("DATABASE_URL").unwrap();
assert_eq!(
"postgres://root:1234@localhost:5432/refinery",
build_db_url("postgres", &config)
);
}

#[test]
fn builds_db_env_var_failure() {
std::env::set_var("DATABASE_URL", "this_is_not_an_url");
let config = Config::from_env_var("DATABASE_URL");
assert!(config.is_err());
}
}
2 changes: 1 addition & 1 deletion refinery_core/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ pub(crate) const GET_LAST_APPLIED_MIGRATION_QUERY: &str =

#[cfg(test)]
mod tests {
use super::{check_missing_divergent, Error, Kind, Migration};
use super::{check_missing_divergent, Kind, Migration};

fn get_migrations() -> Vec<Migration> {
let migration1 = Migration::unapplied(
Expand Down

0 comments on commit 947dd26

Please sign in to comment.