generated from pure_sagacity/rust-starter
switched to using the Store trait
also added another dependency for expanding ~ paths
This commit is contained in:
Generated
+48
@@ -448,6 +448,7 @@ dependencies = [
|
|||||||
"colored",
|
"colored",
|
||||||
"crypto",
|
"crypto",
|
||||||
"diesel",
|
"diesel",
|
||||||
|
"dirs",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"hex",
|
"hex",
|
||||||
"keyring",
|
"keyring",
|
||||||
@@ -653,6 +654,27 @@ dependencies = [
|
|||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||||
|
dependencies = [
|
||||||
|
"dirs-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs-sys"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"option-ext",
|
||||||
|
"redox_users",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dotenvy"
|
name = "dotenvy"
|
||||||
version = "0.15.7"
|
version = "0.15.7"
|
||||||
@@ -1024,6 +1046,15 @@ version = "0.2.186"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libredox"
|
||||||
|
version = "0.1.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libsqlite3-sys"
|
name = "libsqlite3-sys"
|
||||||
version = "0.37.0"
|
version = "0.37.0"
|
||||||
@@ -1171,6 +1202,12 @@ version = "1.70.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "option-ext"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ordered-stream"
|
name = "ordered-stream"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -1322,6 +1359,17 @@ dependencies = [
|
|||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_users"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.2.17",
|
||||||
|
"libredox",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.13.0"
|
version = "1.13.0"
|
||||||
|
|||||||
@@ -13,17 +13,18 @@ diesel = { version = "2.2.0", features = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
] }
|
] }
|
||||||
clap = { version = "4.6.1", features = ["derive", "cargo"] }
|
clap = { version = "4.6.1", features = ["derive", "cargo"] }
|
||||||
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
tokio = { version = "1.52.3", features = ["full"] }
|
tokio = { version = "1.52.3", features = ["full"] }
|
||||||
|
uuid = { version = "1.23.5", features = ["v4"] }
|
||||||
crypto = { path = "../crypto" }
|
crypto = { path = "../crypto" }
|
||||||
keyring = "4.1.4"
|
keyring = "4.1.4"
|
||||||
colored = "3.1.1"
|
colored = "3.1.1"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
chrono = "0.4.45"
|
chrono = "0.4.45"
|
||||||
dotenvy = "0.15"
|
dotenvy = "0.15"
|
||||||
uuid = { version = "1.23.5", features = ["v4"] }
|
|
||||||
hex = "0.4"
|
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
|
||||||
toml = "1.1.2"
|
toml = "1.1.2"
|
||||||
|
hex = "0.4"
|
||||||
|
dirs = "6"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "harbor"
|
name = "harbor"
|
||||||
|
|||||||
+8
-232
@@ -10,9 +10,10 @@ use keyring::Entry;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
use std::path::PathBuf;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
mod db;
|
mod db;
|
||||||
mod store;
|
pub mod store;
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, Clone, Copy, PartialEq, Eq, Deserialize, diesel::AsExpression, diesel::FromSqlRow,
|
Debug, Clone, Copy, PartialEq, Eq, Deserialize, diesel::AsExpression, diesel::FromSqlRow,
|
||||||
@@ -175,30 +176,6 @@ pub fn parse_secret_pairs(raw: &[String]) -> Result<Vec<(String, String)>, Box<d
|
|||||||
Ok(pairs)
|
Ok(pairs)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format_doppler_set_command(secrets: &[(String, String)]) -> String {
|
|
||||||
let mut command = String::from("doppler secrets set");
|
|
||||||
|
|
||||||
if secrets.is_empty() {
|
|
||||||
return command;
|
|
||||||
}
|
|
||||||
|
|
||||||
command.push_str(" \\");
|
|
||||||
|
|
||||||
for (index, (key, value)) in secrets.iter().enumerate() {
|
|
||||||
command.push_str("\n ");
|
|
||||||
command.push_str(key);
|
|
||||||
command.push_str("=\"");
|
|
||||||
command.push_str(value);
|
|
||||||
command.push('"');
|
|
||||||
|
|
||||||
if index + 1 != secrets.len() {
|
|
||||||
command.push_str(" \\");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
command
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub enum ProjectCommands {
|
pub enum ProjectCommands {
|
||||||
List {},
|
List {},
|
||||||
@@ -206,217 +183,16 @@ pub enum ProjectCommands {
|
|||||||
Delete { name: String },
|
Delete { name: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
const DB_URL: &str = "/Users/Maaz/Documents/Git/harbor/crates/cli/harbor.db";
|
pub fn expand_tilde(path: &str) -> String {
|
||||||
|
if let Some(stripped) = path.strip_prefix("~/") {
|
||||||
pub mod interactions {
|
if let Some(home) = dirs::home_dir() {
|
||||||
use super::DB_URL;
|
return home.join(stripped).to_string_lossy().to_string();
|
||||||
use super::db::{establish_connection, models::Project};
|
|
||||||
use crate::Environment;
|
|
||||||
use crate::db::schema::projects::id;
|
|
||||||
use diesel::dsl::{insert_into, update};
|
|
||||||
use diesel::result::{DatabaseErrorKind, Error as DieselError};
|
|
||||||
use diesel::{ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl, SelectableHelper};
|
|
||||||
use std::error::Error;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
|
||||||
|
|
||||||
fn db_url() -> String {
|
|
||||||
std::env::var("DATABASE_URL").unwrap_or_else(|_| DB_URL.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn construct_error(message: &str) -> Result<()> {
|
|
||||||
Err(Box::new(std::io::Error::new(
|
|
||||||
std::io::ErrorKind::AlreadyExists,
|
|
||||||
message.to_string(),
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn project_exists(proj_name: &str) -> Result<bool> {
|
|
||||||
use super::db::schema::projects::dsl::{name, projects};
|
|
||||||
let mut conn = establish_connection(db_url());
|
|
||||||
|
|
||||||
let existing_project = projects
|
|
||||||
.filter(name.eq(proj_name))
|
|
||||||
.select(Project::as_select())
|
|
||||||
.first::<Project>(&mut conn)
|
|
||||||
.optional()?;
|
|
||||||
|
|
||||||
Ok(existing_project.is_some())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_project(proj_name: &str) -> Result<()> {
|
|
||||||
use super::db::schema::projects::dsl::{created_at, id, name, projects};
|
|
||||||
let mut conn = establish_connection(db_url());
|
|
||||||
|
|
||||||
if project_exists(proj_name)? {
|
|
||||||
return construct_error("Project already exists");
|
|
||||||
}
|
|
||||||
|
|
||||||
let proj_id = Uuid::new_v4().to_string();
|
|
||||||
|
|
||||||
match insert_into(projects)
|
|
||||||
.values((
|
|
||||||
id.eq(proj_id),
|
|
||||||
name.eq(proj_name),
|
|
||||||
created_at.eq(chrono::Utc::now().naive_utc()),
|
|
||||||
))
|
|
||||||
.execute(&mut conn)
|
|
||||||
{
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _)) => {
|
|
||||||
construct_error("Project already exists")
|
|
||||||
}
|
|
||||||
Err(err) => Err(Box::new(err)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_project(proj_name: &str) -> Result<()> {
|
path.to_string()
|
||||||
use super::db::schema::projects::dsl::{name, projects};
|
|
||||||
let mut conn = establish_connection(db_url());
|
|
||||||
|
|
||||||
if !project_exists(proj_name)? {
|
|
||||||
return construct_error("Project does not exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
diesel::delete(projects.filter(name.eq(proj_name))).execute(&mut conn)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_projects() -> Result<Vec<Project>> {
|
|
||||||
use super::db::schema::projects::dsl::projects;
|
|
||||||
let mut conn = establish_connection(db_url());
|
|
||||||
|
|
||||||
let results = projects
|
|
||||||
.select(Project::as_select())
|
|
||||||
.load::<Project>(&mut conn)?;
|
|
||||||
|
|
||||||
Ok(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn secret_exists(
|
|
||||||
proj_id: &str,
|
|
||||||
secret_name: &str,
|
|
||||||
environment: Environment,
|
|
||||||
) -> Result<bool> {
|
|
||||||
use super::db::schema::secrets::dsl::{config, id, name, project_id, secrets};
|
|
||||||
let mut conn = establish_connection(db_url());
|
|
||||||
|
|
||||||
let existing_secret = secrets
|
|
||||||
.filter(project_id.eq(proj_id))
|
|
||||||
.filter(name.eq(secret_name))
|
|
||||||
.filter(config.eq(environment))
|
|
||||||
.select(id)
|
|
||||||
.first::<String>(&mut conn)
|
|
||||||
.optional()?;
|
|
||||||
|
|
||||||
Ok(existing_secret.is_some())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_project_id(proj_name: &str) -> Result<String> {
|
|
||||||
use super::db::schema::projects::dsl::{id, name, projects};
|
|
||||||
let mut conn = establish_connection(db_url());
|
|
||||||
|
|
||||||
let project_id = projects
|
|
||||||
.filter(name.eq(proj_name))
|
|
||||||
.select(id)
|
|
||||||
.first::<String>(&mut conn)
|
|
||||||
.optional()?;
|
|
||||||
|
|
||||||
match project_id {
|
|
||||||
Some(pid) => Ok(pid),
|
|
||||||
None => Err(Box::new(std::io::Error::new(
|
|
||||||
std::io::ErrorKind::NotFound,
|
|
||||||
"Project not found",
|
|
||||||
))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_secret(
|
|
||||||
proj_id: &str,
|
|
||||||
secret_name: &str,
|
|
||||||
secret_value: Vec<u8>,
|
|
||||||
conf: Environment,
|
|
||||||
non: crypto::Nonce,
|
|
||||||
) -> Result<()> {
|
|
||||||
use super::db::schema::secrets::dsl::{
|
|
||||||
config, created_at, id, name, nonce, project_id, secret, secrets,
|
|
||||||
};
|
|
||||||
let mut conn = establish_connection(db_url());
|
|
||||||
|
|
||||||
if secret_exists(proj_id, secret_name, conf)? {
|
|
||||||
update(
|
|
||||||
secrets
|
|
||||||
.filter(project_id.eq(proj_id))
|
|
||||||
.filter(name.eq(secret_name))
|
|
||||||
.filter(config.eq(conf)),
|
|
||||||
)
|
|
||||||
.set((secret.eq(secret_value), nonce.eq(non.to_vec())))
|
|
||||||
.execute(&mut conn)?;
|
|
||||||
} else {
|
|
||||||
insert_into(secrets)
|
|
||||||
.values((
|
|
||||||
id.eq(Uuid::new_v4().to_string()),
|
|
||||||
name.eq(secret_name),
|
|
||||||
secret.eq(secret_value),
|
|
||||||
project_id.eq(proj_id),
|
|
||||||
config.eq(conf),
|
|
||||||
nonce.eq(non.to_vec()),
|
|
||||||
created_at.eq(chrono::Utc::now().naive_utc()),
|
|
||||||
))
|
|
||||||
.execute(&mut conn)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_project_secrets(
|
|
||||||
proj_id: &str,
|
|
||||||
environment: Environment,
|
|
||||||
) -> Result<Vec<(String, Vec<u8>, Vec<u8>)>> {
|
|
||||||
use super::db::schema::secrets::dsl::{config, name, nonce, project_id, secret, secrets};
|
|
||||||
let mut conn = establish_connection(db_url());
|
|
||||||
|
|
||||||
let results = secrets
|
|
||||||
.filter(project_id.eq(proj_id))
|
|
||||||
.filter(config.eq(environment))
|
|
||||||
.select((name, secret, nonce))
|
|
||||||
.load::<(String, Vec<u8>, Vec<u8>)>(&mut conn)?;
|
|
||||||
|
|
||||||
Ok(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_secret(proj_id: &str, secret_name: &str, environment: Environment) -> Result<()> {
|
|
||||||
use super::db::schema::secrets::dsl::{config, id, name, project_id, secrets};
|
|
||||||
let mut conn = establish_connection(db_url());
|
|
||||||
|
|
||||||
let existing_secret = secrets
|
|
||||||
.filter(project_id.eq(proj_id))
|
|
||||||
.filter(name.eq(secret_name))
|
|
||||||
.filter(config.eq(environment))
|
|
||||||
.select(id)
|
|
||||||
.first::<String>(&mut conn)
|
|
||||||
.optional()?;
|
|
||||||
|
|
||||||
if existing_secret.is_none() {
|
|
||||||
return Err(Box::new(std::io::Error::new(
|
|
||||||
std::io::ErrorKind::NotFound,
|
|
||||||
"Secret does not exist",
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
diesel::delete(
|
|
||||||
secrets
|
|
||||||
.filter(project_id.eq(proj_id))
|
|
||||||
.filter(name.eq(secret_name))
|
|
||||||
.filter(config.eq(environment)),
|
|
||||||
)
|
|
||||||
.execute(&mut conn)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gen_or_get_key() -> Result<crypto::Key, Box<dyn Error>> {
|
pub fn gen_or_get_key() -> Result<crypto::Key, Box<dyn Error>> {
|
||||||
let entry = Entry::new("harbor", "encryption-key")?;
|
let entry = Entry::new("harbor", "encryption-key")?;
|
||||||
|
|
||||||
|
|||||||
+27
-35
@@ -6,14 +6,11 @@ use clap::crate_version;
|
|||||||
use cli::Cli;
|
use cli::Cli;
|
||||||
use cli::Environment;
|
use cli::Environment;
|
||||||
use cli::config::{Config, ConfigError};
|
use cli::config::{Config, ConfigError};
|
||||||
|
use cli::expand_tilde;
|
||||||
use cli::gen_or_get_key;
|
use cli::gen_or_get_key;
|
||||||
use cli::get_input;
|
use cli::get_input;
|
||||||
use cli::interactions::delete_secret;
|
use cli::store::Store;
|
||||||
use cli::interactions::get_project_id;
|
use cli::store::sqlite::SqliteStore;
|
||||||
use cli::interactions::get_project_secrets;
|
|
||||||
use cli::interactions::get_projects;
|
|
||||||
use cli::interactions::secret_exists;
|
|
||||||
use cli::interactions::set_secret;
|
|
||||||
use colored::*;
|
use colored::*;
|
||||||
use crypto::encrypt;
|
use crypto::encrypt;
|
||||||
|
|
||||||
@@ -47,8 +44,10 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let db_path = expand_tilde("~/.local/share/harbor/database.db");
|
||||||
let config_path = root.join(".harbor.toml");
|
let config_path = root.join(".harbor.toml");
|
||||||
let has_config = config_path.exists();
|
let has_config = config_path.exists();
|
||||||
|
let store = SqliteStore::new(db_path);
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Some(c) => match c {
|
Some(c) => match c {
|
||||||
@@ -69,7 +68,7 @@ fn main() {
|
|||||||
None => config.default_env.into(),
|
None => config.default_env.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let project = match get_project_id(&config.name) {
|
let project = match store.get_project_id(&config.name) {
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
print_error(format!(
|
print_error(format!(
|
||||||
@@ -80,7 +79,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let secrets = match get_project_secrets(&project, environment) {
|
let secrets = match store.get_project_secrets(&project, environment) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
print_error(format!("Error getting secrets: {}", e));
|
print_error(format!("Error getting secrets: {}", e));
|
||||||
@@ -156,7 +155,7 @@ fn main() {
|
|||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let project = match get_project_id(&config.name) {
|
let project = match store.get_project_id(&config.name) {
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
print_error(format!(
|
print_error(format!(
|
||||||
@@ -168,7 +167,7 @@ fn main() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for pair in pairs {
|
for pair in pairs {
|
||||||
let existed = match secret_exists(&project, &pair.0, environment) {
|
let existed = match store.secret_exists(&project, &pair.0, environment) {
|
||||||
Ok(exists) => exists,
|
Ok(exists) => exists,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
print_error(format!(
|
print_error(format!(
|
||||||
@@ -180,11 +179,8 @@ fn main() {
|
|||||||
};
|
};
|
||||||
if existed {
|
if existed {
|
||||||
let confirmation = match get_input(
|
let confirmation = match get_input(
|
||||||
format!(
|
format!("Secret '{}' already exists. Overwrite? (y/N)", pair.0)
|
||||||
"Secret '{}' already exists. Overwrite? (y/N)",
|
.yellow(),
|
||||||
pair.0
|
|
||||||
)
|
|
||||||
.yellow(),
|
|
||||||
':',
|
':',
|
||||||
false,
|
false,
|
||||||
) {
|
) {
|
||||||
@@ -198,12 +194,8 @@ fn main() {
|
|||||||
if confirmation.to_lowercase() != "y" {
|
if confirmation.to_lowercase() != "y" {
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
format!(
|
format!("Skipped secret '{}' in {}.", pair.0, environment.as_str())
|
||||||
"Skipped secret '{}' in {}.",
|
.dimmed()
|
||||||
pair.0,
|
|
||||||
environment.as_str()
|
|
||||||
)
|
|
||||||
.dimmed()
|
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -226,7 +218,7 @@ fn main() {
|
|||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
match set_secret(&project, &pair.0, encrypted, environment, nonce) {
|
match store.set_secret(&project, &pair.0, encrypted, environment, nonce) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
let verb = if existed { "Updated" } else { "Set" };
|
let verb = if existed { "Updated" } else { "Set" };
|
||||||
println!(
|
println!(
|
||||||
@@ -263,7 +255,7 @@ fn main() {
|
|||||||
},
|
},
|
||||||
None => config.default_env.into(),
|
None => config.default_env.into(),
|
||||||
};
|
};
|
||||||
let proj_id = match get_project_id(&config.name) {
|
let proj_id = match store.get_project_id(&config.name) {
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
print_error(format!(
|
print_error(format!(
|
||||||
@@ -291,7 +283,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for key in cleaned {
|
for key in cleaned {
|
||||||
match delete_secret(&proj_id, &key, environment) {
|
match store.delete_secret(&proj_id, &key, environment) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
println!("Deleted secret for key '{}'", key);
|
println!("Deleted secret for key '{}'", key);
|
||||||
}
|
}
|
||||||
@@ -314,7 +306,7 @@ fn main() {
|
|||||||
},
|
},
|
||||||
None => config.default_env.into(),
|
None => config.default_env.into(),
|
||||||
};
|
};
|
||||||
let proj_id = match get_project_id(&config.name) {
|
let proj_id = match store.get_project_id(&config.name) {
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
print_error(format!(
|
print_error(format!(
|
||||||
@@ -334,7 +326,7 @@ fn main() {
|
|||||||
cmd.args(&after[1..]);
|
cmd.args(&after[1..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let secrets = match get_project_secrets(&proj_id, environment) {
|
let secrets = match store.get_project_secrets(&proj_id, environment) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
print_error(format!("Error getting secrets: {}", e));
|
print_error(format!("Error getting secrets: {}", e));
|
||||||
@@ -392,7 +384,7 @@ fn main() {
|
|||||||
Commands::List {} => {
|
Commands::List {} => {
|
||||||
// Get all secrets, then use colorize and split them between environments, and print them out in a table format.
|
// Get all secrets, then use colorize and split them between environments, and print them out in a table format.
|
||||||
let config = require_config(&root);
|
let config = require_config(&root);
|
||||||
let proj_id = match get_project_id(&config.name) {
|
let proj_id = match store.get_project_id(&config.name) {
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
print_error(format!(
|
print_error(format!(
|
||||||
@@ -403,7 +395,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let secrets = match get_project_secrets(&proj_id, Environment::Dev) {
|
let secrets = match store.get_project_secrets(&proj_id, Environment::Dev) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
print_error(format!("Error getting secrets: {}", e));
|
print_error(format!("Error getting secrets: {}", e));
|
||||||
@@ -429,7 +421,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
Commands::Project { command } => match command {
|
Commands::Project { command } => match command {
|
||||||
ProjectCommands::List {} => {
|
ProjectCommands::List {} => {
|
||||||
let projects = match get_projects() {
|
let projects = match store.get_projects() {
|
||||||
Ok(projects) => projects,
|
Ok(projects) => projects,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
print_error(format!("Error getting projects: {}", e));
|
print_error(format!("Error getting projects: {}", e));
|
||||||
@@ -449,7 +441,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match cli::interactions::create_project(&project_name) {
|
match store.create_project(&project_name) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
println!("{}", "Project created successfully.".bright_green().bold())
|
println!("{}", "Project created successfully.".bright_green().bold())
|
||||||
}
|
}
|
||||||
@@ -461,7 +453,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
ProjectCommands::Delete { name } => {
|
ProjectCommands::Delete { name } => {
|
||||||
// We'll check if the project exists before prompting for confirmation
|
// We'll check if the project exists before prompting for confirmation
|
||||||
let exists = match cli::interactions::project_exists(&name) {
|
let exists = match store.project_exists(&name) {
|
||||||
Ok(exists) => exists,
|
Ok(exists) => exists,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
print_error(format!("Error checking if project exists: {}", e));
|
print_error(format!("Error checking if project exists: {}", e));
|
||||||
@@ -492,7 +484,7 @@ fn main() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if confirmation.to_lowercase() == "y" {
|
if confirmation.to_lowercase() == "y" {
|
||||||
match cli::interactions::delete_project(&name) {
|
match store.delete_project(&name) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
@@ -525,7 +517,7 @@ fn main() {
|
|||||||
},
|
},
|
||||||
None => config.default_env.into(),
|
None => config.default_env.into(),
|
||||||
};
|
};
|
||||||
let proj_id = match get_project_id(&config.name) {
|
let proj_id = match store.get_project_id(&config.name) {
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
print_error(format!(
|
print_error(format!(
|
||||||
@@ -551,7 +543,7 @@ fn main() {
|
|||||||
cmd.arg(command);
|
cmd.arg(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
let secrets = match get_project_secrets(&proj_id, environment) {
|
let secrets = match store.get_project_secrets(&proj_id, environment) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
print_error(format!("Error getting secrets: {}", e));
|
print_error(format!("Error getting secrets: {}", e));
|
||||||
@@ -618,7 +610,7 @@ fn main() {
|
|||||||
use std::process;
|
use std::process;
|
||||||
// Setup will get all projects and prompt the user to select one,
|
// Setup will get all projects and prompt the user to select one,
|
||||||
// then create a .harbor.toml file in the current directory with the selected project name.
|
// then create a .harbor.toml file in the current directory with the selected project name.
|
||||||
let projects = match get_projects() {
|
let projects = match store.get_projects() {
|
||||||
Ok(projects) => projects,
|
Ok(projects) => projects,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
print_error(format!("Error getting projects: {}", e));
|
print_error(format!("Error getting projects: {}", e));
|
||||||
|
|||||||
@@ -3,5 +3,35 @@
|
|||||||
// With this trait, we can implement other savers like Postgres, MySQL, etc.
|
// With this trait, we can implement other savers like Postgres, MySQL, etc.
|
||||||
// Or even third party services like Hashicorp Vault, AWS Secrets Manager, etc.
|
// Or even third party services like Hashicorp Vault, AWS Secrets Manager, etc.
|
||||||
|
|
||||||
// For now, I'll just get an MVP in place.
|
use std::error::Error;
|
||||||
pub trait Store {}
|
|
||||||
|
pub mod sqlite;
|
||||||
|
|
||||||
|
use crate::{Environment, db::models::Project};
|
||||||
|
|
||||||
|
type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
||||||
|
|
||||||
|
pub trait Store {
|
||||||
|
fn project_exists(&self, proj_name: &str) -> Result<bool>;
|
||||||
|
fn create_project(&self, proj_name: &str) -> Result<()>;
|
||||||
|
fn delete_project(&self, proj_name: &str) -> Result<()>;
|
||||||
|
fn get_projects(&self) -> Result<Vec<Project>>;
|
||||||
|
fn get_project_id(&self, proj_name: &str) -> Result<String>;
|
||||||
|
fn secret_exists(&self, proj_id: &str, secret_name: &str, environment: Environment)
|
||||||
|
-> Result<bool>;
|
||||||
|
fn set_secret(
|
||||||
|
&self,
|
||||||
|
proj_id: &str,
|
||||||
|
secret_name: &str,
|
||||||
|
secret_value: Vec<u8>,
|
||||||
|
conf: Environment,
|
||||||
|
non: crypto::Nonce,
|
||||||
|
) -> Result<()>;
|
||||||
|
fn get_project_secrets(
|
||||||
|
&self,
|
||||||
|
proj_id: &str,
|
||||||
|
environment: Environment,
|
||||||
|
) -> Result<Vec<(String, Vec<u8>, Vec<u8>)>>;
|
||||||
|
fn delete_secret(&self, proj_id: &str, secret_name: &str, environment: Environment)
|
||||||
|
-> Result<()>;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,220 @@
|
|||||||
|
use crate::db::models::Project;
|
||||||
|
use crate::store::Store;
|
||||||
|
use diesel::dsl::{insert_into, update};
|
||||||
|
use diesel::result::{DatabaseErrorKind, Error as DieselError};
|
||||||
|
use diesel::{ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl, SelectableHelper};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub struct SqliteStore {
|
||||||
|
database_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SqliteStore {
|
||||||
|
pub fn new(database_url: String) -> Self {
|
||||||
|
SqliteStore { database_url }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn db_url(&self) -> String {
|
||||||
|
if self.database_url.is_empty() {
|
||||||
|
std::env::var("DATABASE_URL").expect("NO DATABASE URL WAS SET")
|
||||||
|
} else {
|
||||||
|
self.database_url.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn construct_error(message: &str) -> super::Result<()> {
|
||||||
|
Err(Box::new(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::AlreadyExists,
|
||||||
|
message.to_string(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Store for SqliteStore {
|
||||||
|
fn create_project(&self, proj_name: &str) -> super::Result<()> {
|
||||||
|
use crate::db::schema::projects::dsl::{created_at, id, name, projects};
|
||||||
|
let mut conn = crate::db::establish_connection(self.db_url());
|
||||||
|
|
||||||
|
if self.project_exists(proj_name)? {
|
||||||
|
return construct_error("Project already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
let proj_id = Uuid::new_v4().to_string();
|
||||||
|
|
||||||
|
match insert_into(projects)
|
||||||
|
.values((
|
||||||
|
id.eq(proj_id),
|
||||||
|
name.eq(proj_name),
|
||||||
|
created_at.eq(chrono::Utc::now().naive_utc()),
|
||||||
|
))
|
||||||
|
.execute(&mut conn)
|
||||||
|
{
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _)) => {
|
||||||
|
construct_error("Project already exists")
|
||||||
|
}
|
||||||
|
Err(err) => Err(Box::new(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_project(&self, proj_name: &str) -> super::Result<()> {
|
||||||
|
use crate::db::schema::projects::dsl::{name, projects};
|
||||||
|
let mut conn = crate::db::establish_connection(self.db_url());
|
||||||
|
|
||||||
|
if !self.project_exists(proj_name)? {
|
||||||
|
return construct_error("Project does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::delete(projects.filter(name.eq(proj_name))).execute(&mut conn)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn delete_secret(
|
||||||
|
&self,
|
||||||
|
proj_id: &str,
|
||||||
|
secret_name: &str,
|
||||||
|
environment: crate::Environment,
|
||||||
|
) -> super::Result<()> {
|
||||||
|
use crate::db::schema::secrets::dsl::{config, id, name, project_id, secrets};
|
||||||
|
let mut conn = crate::db::establish_connection(self.db_url());
|
||||||
|
|
||||||
|
let existing_secret = secrets
|
||||||
|
.filter(project_id.eq(proj_id))
|
||||||
|
.filter(name.eq(secret_name))
|
||||||
|
.filter(config.eq(environment))
|
||||||
|
.select(id)
|
||||||
|
.first::<String>(&mut conn)
|
||||||
|
.optional()?;
|
||||||
|
|
||||||
|
if existing_secret.is_none() {
|
||||||
|
return Err(Box::new(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::NotFound,
|
||||||
|
"Secret does not exist",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::delete(
|
||||||
|
secrets
|
||||||
|
.filter(project_id.eq(proj_id))
|
||||||
|
.filter(name.eq(secret_name))
|
||||||
|
.filter(config.eq(environment)),
|
||||||
|
)
|
||||||
|
.execute(&mut conn)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn get_project_id(&self, proj_name: &str) -> super::Result<String> {
|
||||||
|
use crate::db::schema::projects::dsl::{id, name, projects};
|
||||||
|
let mut conn = crate::db::establish_connection(self.db_url());
|
||||||
|
|
||||||
|
let project_id = projects
|
||||||
|
.filter(name.eq(proj_name))
|
||||||
|
.select(id)
|
||||||
|
.first::<String>(&mut conn)
|
||||||
|
.optional()?;
|
||||||
|
|
||||||
|
match project_id {
|
||||||
|
Some(pid) => Ok(pid),
|
||||||
|
None => Err(Box::new(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::NotFound,
|
||||||
|
"Project not found",
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get_project_secrets(
|
||||||
|
&self,
|
||||||
|
proj_id: &str,
|
||||||
|
environment: crate::Environment,
|
||||||
|
) -> super::Result<Vec<(String, Vec<u8>, Vec<u8>)>> {
|
||||||
|
use crate::db::schema::secrets::dsl::{config, name, nonce, project_id, secret, secrets};
|
||||||
|
let mut conn = crate::db::establish_connection(self.db_url());
|
||||||
|
|
||||||
|
let results = secrets
|
||||||
|
.filter(project_id.eq(proj_id))
|
||||||
|
.filter(config.eq(environment))
|
||||||
|
.select((name, secret, nonce))
|
||||||
|
.load::<(String, Vec<u8>, Vec<u8>)>(&mut conn)?;
|
||||||
|
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
fn get_projects(&self) -> super::Result<Vec<Project>> {
|
||||||
|
use crate::db::schema::projects::dsl::projects;
|
||||||
|
let mut conn = crate::db::establish_connection(self.db_url());
|
||||||
|
|
||||||
|
let results = projects
|
||||||
|
.select(Project::as_select())
|
||||||
|
.load::<Project>(&mut conn)?;
|
||||||
|
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn project_exists(&self, proj_name: &str) -> super::Result<bool> {
|
||||||
|
use crate::db::schema::projects::dsl::{name, projects};
|
||||||
|
let mut conn = crate::db::establish_connection(self.db_url());
|
||||||
|
|
||||||
|
let existing_project = projects
|
||||||
|
.filter(name.eq(proj_name))
|
||||||
|
.select(Project::as_select())
|
||||||
|
.first::<Project>(&mut conn)
|
||||||
|
.optional()?;
|
||||||
|
|
||||||
|
Ok(existing_project.is_some())
|
||||||
|
}
|
||||||
|
fn secret_exists(
|
||||||
|
&self,
|
||||||
|
proj_id: &str,
|
||||||
|
secret_name: &str,
|
||||||
|
environment: crate::Environment,
|
||||||
|
) -> super::Result<bool> {
|
||||||
|
use crate::db::schema::secrets::dsl::{config, id, name, project_id, secrets};
|
||||||
|
let mut conn = crate::db::establish_connection(self.db_url());
|
||||||
|
|
||||||
|
let existing_secret = secrets
|
||||||
|
.filter(project_id.eq(proj_id))
|
||||||
|
.filter(name.eq(secret_name))
|
||||||
|
.filter(config.eq(environment))
|
||||||
|
.select(id)
|
||||||
|
.first::<String>(&mut conn)
|
||||||
|
.optional()?;
|
||||||
|
|
||||||
|
Ok(existing_secret.is_some())
|
||||||
|
}
|
||||||
|
fn set_secret(
|
||||||
|
&self,
|
||||||
|
proj_id: &str,
|
||||||
|
secret_name: &str,
|
||||||
|
secret_value: Vec<u8>,
|
||||||
|
conf: crate::Environment,
|
||||||
|
non: crypto::Nonce,
|
||||||
|
) -> super::Result<()> {
|
||||||
|
use crate::db::schema::secrets::dsl::{
|
||||||
|
config, created_at, id, name, nonce, project_id, secret, secrets,
|
||||||
|
};
|
||||||
|
let mut conn = crate::db::establish_connection(self.db_url());
|
||||||
|
|
||||||
|
if self.secret_exists(proj_id, secret_name, conf)? {
|
||||||
|
update(
|
||||||
|
secrets
|
||||||
|
.filter(project_id.eq(proj_id))
|
||||||
|
.filter(name.eq(secret_name))
|
||||||
|
.filter(config.eq(conf)),
|
||||||
|
)
|
||||||
|
.set((secret.eq(secret_value), nonce.eq(non.to_vec())))
|
||||||
|
.execute(&mut conn)?;
|
||||||
|
} else {
|
||||||
|
insert_into(secrets)
|
||||||
|
.values((
|
||||||
|
id.eq(Uuid::new_v4().to_string()),
|
||||||
|
name.eq(secret_name),
|
||||||
|
secret.eq(secret_value),
|
||||||
|
project_id.eq(proj_id),
|
||||||
|
config.eq(conf),
|
||||||
|
nonce.eq(non.to_vec()),
|
||||||
|
created_at.eq(chrono::Utc::now().naive_utc()),
|
||||||
|
))
|
||||||
|
.execute(&mut conn)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
use cli::Environment;
|
use cli::Environment;
|
||||||
use cli::config::Config;
|
use cli::config::Config;
|
||||||
use cli::interactions::{
|
use cli::parse_secret_pairs;
|
||||||
create_project, delete_project, delete_secret, get_project_id, get_project_secrets,
|
use cli::store::Store;
|
||||||
get_projects, project_exists, secret_exists, set_secret,
|
use cli::store::sqlite::SqliteStore;
|
||||||
};
|
|
||||||
use cli::{format_doppler_set_command, parse_secret_pairs};
|
|
||||||
use diesel::connection::SimpleConnection;
|
use diesel::connection::SimpleConnection;
|
||||||
use diesel::{Connection, SqliteConnection};
|
use diesel::{Connection, SqliteConnection};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -91,22 +89,6 @@ fn parse_secret_pairs_handles_valid_and_invalid_input() {
|
|||||||
assert_eq!(err.to_string(), "Secret key cannot be empty");
|
assert_eq!(err.to_string(), "Secret key cannot be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn format_doppler_set_command_outputs_expected_format() {
|
|
||||||
let empty = format_doppler_set_command(&[]);
|
|
||||||
assert_eq!(empty, "doppler secrets set");
|
|
||||||
|
|
||||||
let one = format_doppler_set_command(&[("API_KEY".to_string(), "abc".to_string())]);
|
|
||||||
assert_eq!(one, "doppler secrets set \\\n API_KEY=\"abc\"");
|
|
||||||
|
|
||||||
let many = format_doppler_set_command(&[
|
|
||||||
("A".to_string(), "1".to_string()),
|
|
||||||
("B".to_string(), "2".to_string()),
|
|
||||||
]);
|
|
||||||
|
|
||||||
assert_eq!(many, "doppler secrets set \\\n A=\"1\" \\\n B=\"2\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn config_reads_valid_file_and_reports_errors() {
|
fn config_reads_valid_file_and_reports_errors() {
|
||||||
let temp_root = std::env::temp_dir().join(format!("harbor_config_{}", Uuid::new_v4()));
|
let temp_root = std::env::temp_dir().join(format!("harbor_config_{}", Uuid::new_v4()));
|
||||||
@@ -140,55 +122,76 @@ fn config_reads_valid_file_and_reports_errors() {
|
|||||||
fn project_and_secret_lifecycle() {
|
fn project_and_secret_lifecycle() {
|
||||||
let _guard = test_lock().lock().expect("lock tests");
|
let _guard = test_lock().lock().expect("lock tests");
|
||||||
let db_path = setup_test_db();
|
let db_path = setup_test_db();
|
||||||
|
let store = SqliteStore::new(String::new());
|
||||||
|
|
||||||
assert!(!project_exists("alpha").expect("project exists"));
|
assert!(!store.project_exists("alpha").expect("project exists"));
|
||||||
|
|
||||||
create_project("alpha").expect("create project");
|
store.create_project("alpha").expect("create project");
|
||||||
assert!(project_exists("alpha").expect("project exists"));
|
assert!(store.project_exists("alpha").expect("project exists"));
|
||||||
|
|
||||||
let project_id = get_project_id("alpha").expect("get project id");
|
let project_id = store.get_project_id("alpha").expect("get project id");
|
||||||
let projects = get_projects().expect("get projects");
|
let projects = store.get_projects().expect("get projects");
|
||||||
assert_eq!(projects.len(), 1);
|
assert_eq!(projects.len(), 1);
|
||||||
assert_eq!(projects[0].name, "alpha");
|
assert_eq!(projects[0].name, "alpha");
|
||||||
|
|
||||||
assert!(!secret_exists(&project_id, "API_KEY", Environment::Dev).expect("secret exists"));
|
assert!(
|
||||||
|
!store
|
||||||
|
.secret_exists(&project_id, "API_KEY", Environment::Dev)
|
||||||
|
.expect("secret exists")
|
||||||
|
);
|
||||||
|
|
||||||
let nonce = crypto::helper::gen_nonce();
|
let nonce = crypto::helper::gen_nonce();
|
||||||
set_secret(
|
store
|
||||||
&project_id,
|
.set_secret(
|
||||||
"API_KEY",
|
&project_id,
|
||||||
b"first".to_vec(),
|
"API_KEY",
|
||||||
Environment::Dev,
|
b"first".to_vec(),
|
||||||
nonce,
|
Environment::Dev,
|
||||||
)
|
nonce,
|
||||||
.expect("set secret");
|
)
|
||||||
|
.expect("set secret");
|
||||||
|
|
||||||
assert!(secret_exists(&project_id, "API_KEY", Environment::Dev).expect("secret exists"));
|
assert!(
|
||||||
|
store
|
||||||
|
.secret_exists(&project_id, "API_KEY", Environment::Dev)
|
||||||
|
.expect("secret exists")
|
||||||
|
);
|
||||||
|
|
||||||
let secrets = get_project_secrets(&project_id, Environment::Dev).expect("get secrets");
|
let secrets = store
|
||||||
|
.get_project_secrets(&project_id, Environment::Dev)
|
||||||
|
.expect("get secrets");
|
||||||
assert_eq!(secrets.len(), 1);
|
assert_eq!(secrets.len(), 1);
|
||||||
assert_eq!(secrets[0].0, "API_KEY");
|
assert_eq!(secrets[0].0, "API_KEY");
|
||||||
assert_eq!(secrets[0].1, b"first".to_vec());
|
assert_eq!(secrets[0].1, b"first".to_vec());
|
||||||
|
|
||||||
let nonce = crypto::helper::gen_nonce();
|
let nonce = crypto::helper::gen_nonce();
|
||||||
set_secret(
|
store
|
||||||
&project_id,
|
.set_secret(
|
||||||
"API_KEY",
|
&project_id,
|
||||||
b"second".to_vec(),
|
"API_KEY",
|
||||||
Environment::Dev,
|
b"second".to_vec(),
|
||||||
nonce,
|
Environment::Dev,
|
||||||
)
|
nonce,
|
||||||
.expect("update secret");
|
)
|
||||||
|
.expect("update secret");
|
||||||
|
|
||||||
let updated = get_project_secrets(&project_id, Environment::Dev).expect("get secrets");
|
let updated = store
|
||||||
|
.get_project_secrets(&project_id, Environment::Dev)
|
||||||
|
.expect("get secrets");
|
||||||
assert_eq!(updated.len(), 1);
|
assert_eq!(updated.len(), 1);
|
||||||
assert_eq!(updated[0].1, b"second".to_vec());
|
assert_eq!(updated[0].1, b"second".to_vec());
|
||||||
|
|
||||||
delete_secret(&project_id, "API_KEY", Environment::Dev).expect("delete secret");
|
store
|
||||||
assert!(!secret_exists(&project_id, "API_KEY", Environment::Dev).expect("secret exists"));
|
.delete_secret(&project_id, "API_KEY", Environment::Dev)
|
||||||
|
.expect("delete secret");
|
||||||
|
assert!(
|
||||||
|
!store
|
||||||
|
.secret_exists(&project_id, "API_KEY", Environment::Dev)
|
||||||
|
.expect("secret exists")
|
||||||
|
);
|
||||||
|
|
||||||
delete_project("alpha").expect("delete project");
|
store.delete_project("alpha").expect("delete project");
|
||||||
assert!(!project_exists("alpha").expect("project exists"));
|
assert!(!store.project_exists("alpha").expect("project exists"));
|
||||||
|
|
||||||
cleanup_test_db(db_path);
|
cleanup_test_db(db_path);
|
||||||
}
|
}
|
||||||
@@ -197,15 +200,18 @@ fn project_and_secret_lifecycle() {
|
|||||||
fn errors_for_missing_project_or_secret() {
|
fn errors_for_missing_project_or_secret() {
|
||||||
let _guard = test_lock().lock().expect("lock tests");
|
let _guard = test_lock().lock().expect("lock tests");
|
||||||
let db_path = setup_test_db();
|
let db_path = setup_test_db();
|
||||||
|
let store = SqliteStore::new(String::new());
|
||||||
|
|
||||||
let err = delete_project("missing").unwrap_err();
|
let err = store.delete_project("missing").unwrap_err();
|
||||||
assert_eq!(err.to_string(), "Project does not exist");
|
assert_eq!(err.to_string(), "Project does not exist");
|
||||||
|
|
||||||
let err = get_project_id("missing").unwrap_err();
|
let err = store.get_project_id("missing").unwrap_err();
|
||||||
assert_eq!(err.to_string(), "Project not found");
|
assert_eq!(err.to_string(), "Project not found");
|
||||||
|
|
||||||
let missing_project_id = Uuid::new_v4().to_string();
|
let missing_project_id = Uuid::new_v4().to_string();
|
||||||
let err = delete_secret(&missing_project_id, "missing", Environment::Dev).unwrap_err();
|
let err = store
|
||||||
|
.delete_secret(&missing_project_id, "missing", Environment::Dev)
|
||||||
|
.unwrap_err();
|
||||||
assert_eq!(err.to_string(), "Secret does not exist");
|
assert_eq!(err.to_string(), "Secret does not exist");
|
||||||
|
|
||||||
cleanup_test_db(db_path);
|
cleanup_test_db(db_path);
|
||||||
|
|||||||
Reference in New Issue
Block a user