-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
## 🎟️ Tracking [SM-1287](https://bitwarden.atlassian.net/browse/SM-1287) ## 📔 Objective <!-- Describe what the purpose of this PR is, for example what bug you're fixing or new feature you're adding. --> The goal of this PR is to refactor `bws/src/main.rs` and the `process_commands()` function. This PR moves the actual CLI interaction for individual commands to the `command` module and appropriate sub-modules. There are plenty of other additions and changes to be made, but I'm trying to keep this PR a clean refactor and we can address other items in separate PR's. Deprecation of the legacy CLI commands will happen in [SM-1175](https://bitwarden.atlassian.net/browse/SM-1175). ## ⏰ Reminders before review - Contributor guidelines followed - All formatters and local linters executed and passed - Written new unit and / or integration tests where applicable - Protected functional changes with optionality (feature flags) - Used internationalization (i18n) for all UI strings - CI builds passed - Communicated to DevOps any deployment requirements - Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team ## 🦮 Reviewer guidelines <!-- Suggested interactions but feel free to use (or not) as you desire! --> - 👍 (`:+1:`) or similar for great changes - 📝 (`:memo:`) or ℹ️ (`:information_source:`) for notes or general info - ❓ (`:question:`) for questions - 🤔 (`:thinking:`) or 💭 (`:thought_balloon:`) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion - 🎨 (`:art:`) for suggestions / improvements - ❌ (`:x:`) or⚠️ (`:warning:`) for more significant problems or concerns needing attention - 🌱 (`:seedling:`) or ♻️ (`:recycle:`) for future improvements or indications of technical debt - ⛏ (`:pick:`) for minor or nitpick changes [SM-1287]: https://bitwarden.atlassian.net/browse/SM-1287?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [SM-1175]: https://bitwarden.atlassian.net/browse/SM-1175?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
- Loading branch information
1 parent
00b2120
commit 4b1d6a1
Showing
6 changed files
with
406 additions
and
222 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
pub(crate) mod project; | ||
pub(crate) mod secret; | ||
|
||
use std::{path::PathBuf, str::FromStr}; | ||
|
||
use bitwarden::auth::AccessToken; | ||
use clap::CommandFactory; | ||
use clap_complete::Shell; | ||
use color_eyre::eyre::{bail, Result}; | ||
|
||
use crate::{config, Cli, ProfileKey}; | ||
|
||
pub(crate) fn completions(shell: Option<Shell>) -> Result<()> { | ||
let Some(shell) = shell.or_else(Shell::from_env) else { | ||
bail!("Couldn't autodetect a valid shell. Run `bws completions --help` for more info."); | ||
}; | ||
|
||
let mut cmd = Cli::command(); | ||
let name = cmd.get_name().to_string(); | ||
clap_complete::generate(shell, &mut cmd, name, &mut std::io::stdout()); | ||
|
||
Ok(()) | ||
} | ||
|
||
pub(crate) fn config( | ||
name: Option<ProfileKey>, | ||
value: Option<String>, | ||
delete: bool, | ||
profile: Option<String>, | ||
access_token: Option<String>, | ||
config_file: Option<PathBuf>, | ||
) -> Result<()> { | ||
let profile = if let Some(profile) = profile { | ||
profile | ||
} else if let Some(access_token) = access_token { | ||
AccessToken::from_str(&access_token)? | ||
.access_token_id | ||
.to_string() | ||
} else { | ||
String::from("default") | ||
}; | ||
|
||
if delete { | ||
config::delete_profile(config_file.as_deref(), profile)?; | ||
println!("Profile deleted successfully!"); | ||
} else { | ||
let (name, value) = match (name, value) { | ||
(None, None) => bail!("Missing `name` and `value`"), | ||
(None, Some(_)) => bail!("Missing `value`"), | ||
(Some(_), None) => bail!("Missing `name`"), | ||
(Some(name), Some(value)) => (name, value), | ||
}; | ||
|
||
config::update_profile(config_file.as_deref(), profile, name, value)?; | ||
println!("Profile updated successfully!"); | ||
}; | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
use bitwarden::{ | ||
secrets_manager::projects::{ | ||
ProjectCreateRequest, ProjectGetRequest, ProjectPutRequest, ProjectsDeleteRequest, | ||
ProjectsListRequest, | ||
}, | ||
Client, | ||
}; | ||
use color_eyre::eyre::{bail, Result}; | ||
use uuid::Uuid; | ||
|
||
use crate::render::{serialize_response, OutputSettings}; | ||
|
||
pub(crate) async fn list( | ||
client: Client, | ||
organization_id: Uuid, | ||
output_settings: OutputSettings, | ||
) -> Result<()> { | ||
let projects = client | ||
.projects() | ||
.list(&ProjectsListRequest { organization_id }) | ||
.await? | ||
.data; | ||
serialize_response(projects, output_settings); | ||
|
||
Ok(()) | ||
} | ||
|
||
pub(crate) async fn get( | ||
client: Client, | ||
project_id: Uuid, | ||
output_settings: OutputSettings, | ||
) -> Result<()> { | ||
let project = client | ||
.projects() | ||
.get(&ProjectGetRequest { id: project_id }) | ||
.await?; | ||
serialize_response(project, output_settings); | ||
|
||
Ok(()) | ||
} | ||
|
||
pub(crate) async fn create( | ||
client: Client, | ||
organization_id: Uuid, | ||
name: String, | ||
output_settings: OutputSettings, | ||
) -> Result<()> { | ||
let project = client | ||
.projects() | ||
.create(&ProjectCreateRequest { | ||
organization_id, | ||
name, | ||
}) | ||
.await?; | ||
serialize_response(project, output_settings); | ||
|
||
Ok(()) | ||
} | ||
|
||
pub(crate) async fn edit( | ||
client: Client, | ||
organization_id: Uuid, | ||
project_id: Uuid, | ||
name: String, | ||
output_settings: OutputSettings, | ||
) -> Result<()> { | ||
let project = client | ||
.projects() | ||
.update(&ProjectPutRequest { | ||
id: project_id, | ||
organization_id, | ||
name, | ||
}) | ||
.await?; | ||
serialize_response(project, output_settings); | ||
|
||
Ok(()) | ||
} | ||
|
||
pub(crate) async fn delete(client: Client, project_ids: Vec<Uuid>) -> Result<()> { | ||
let count = project_ids.len(); | ||
|
||
let result = client | ||
.projects() | ||
.delete(ProjectsDeleteRequest { ids: project_ids }) | ||
.await?; | ||
|
||
let projects_failed: Vec<(Uuid, String)> = result | ||
.data | ||
.into_iter() | ||
.filter_map(|r| r.error.map(|e| (r.id, e))) | ||
.collect(); | ||
let deleted_projects = count - projects_failed.len(); | ||
|
||
match deleted_projects { | ||
2.. => println!("{} projects deleted successfully.", deleted_projects), | ||
1 => println!("{} project deleted successfully.", deleted_projects), | ||
_ => (), | ||
} | ||
|
||
match projects_failed.len() { | ||
2.. => eprintln!("{} projects had errors:", projects_failed.len()), | ||
1 => eprintln!("{} project had an error:", projects_failed.len()), | ||
_ => (), | ||
} | ||
|
||
for project in &projects_failed { | ||
eprintln!("{}: {}", project.0, project.1); | ||
} | ||
|
||
if !projects_failed.is_empty() { | ||
bail!("Errors when attempting to delete projects."); | ||
} | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
use bitwarden::{ | ||
secrets_manager::secrets::{ | ||
SecretCreateRequest, SecretGetRequest, SecretIdentifiersByProjectRequest, | ||
SecretIdentifiersRequest, SecretPutRequest, SecretsDeleteRequest, SecretsGetRequest, | ||
}, | ||
Client, | ||
}; | ||
use color_eyre::eyre::{bail, Result}; | ||
use uuid::Uuid; | ||
|
||
use crate::render::{serialize_response, OutputSettings}; | ||
|
||
#[derive(Debug)] | ||
pub(crate) struct SecretCreateCommandModel { | ||
pub(crate) key: String, | ||
pub(crate) value: String, | ||
pub(crate) note: Option<String>, | ||
pub(crate) project_id: Uuid, | ||
} | ||
|
||
#[derive(Debug)] | ||
pub(crate) struct SecretEditCommandModel { | ||
pub(crate) id: Uuid, | ||
pub(crate) key: Option<String>, | ||
pub(crate) value: Option<String>, | ||
pub(crate) note: Option<String>, | ||
pub(crate) project_id: Option<Uuid>, | ||
} | ||
|
||
pub(crate) async fn list( | ||
client: Client, | ||
organization_id: Uuid, | ||
project_id: Option<Uuid>, | ||
output_settings: OutputSettings, | ||
) -> Result<()> { | ||
let res = if let Some(project_id) = project_id { | ||
client | ||
.secrets() | ||
.list_by_project(&SecretIdentifiersByProjectRequest { project_id }) | ||
.await? | ||
} else { | ||
client | ||
.secrets() | ||
.list(&SecretIdentifiersRequest { organization_id }) | ||
.await? | ||
}; | ||
|
||
let secret_ids = res.data.into_iter().map(|e| e.id).collect(); | ||
let secrets = client | ||
.secrets() | ||
.get_by_ids(SecretsGetRequest { ids: secret_ids }) | ||
.await? | ||
.data; | ||
serialize_response(secrets, output_settings); | ||
|
||
Ok(()) | ||
} | ||
|
||
pub(crate) async fn get( | ||
client: Client, | ||
secret_id: Uuid, | ||
output_settings: OutputSettings, | ||
) -> Result<()> { | ||
let secret = client | ||
.secrets() | ||
.get(&SecretGetRequest { id: secret_id }) | ||
.await?; | ||
serialize_response(secret, output_settings); | ||
|
||
Ok(()) | ||
} | ||
|
||
pub(crate) async fn create( | ||
client: Client, | ||
organization_id: Uuid, | ||
secret: SecretCreateCommandModel, | ||
output_settings: OutputSettings, | ||
) -> Result<()> { | ||
let secret = client | ||
.secrets() | ||
.create(&SecretCreateRequest { | ||
organization_id, | ||
key: secret.key, | ||
value: secret.value, | ||
note: secret.note.unwrap_or_default(), | ||
project_ids: Some(vec![secret.project_id]), | ||
}) | ||
.await?; | ||
serialize_response(secret, output_settings); | ||
|
||
Ok(()) | ||
} | ||
|
||
pub(crate) async fn edit( | ||
client: Client, | ||
organization_id: Uuid, | ||
secret: SecretEditCommandModel, | ||
output_settings: OutputSettings, | ||
) -> Result<()> { | ||
let old_secret = client | ||
.secrets() | ||
.get(&SecretGetRequest { id: secret.id }) | ||
.await?; | ||
|
||
let new_secret = client | ||
.secrets() | ||
.update(&SecretPutRequest { | ||
id: secret.id, | ||
organization_id, | ||
key: secret.key.unwrap_or(old_secret.key), | ||
value: secret.value.unwrap_or(old_secret.value), | ||
note: secret.note.unwrap_or(old_secret.note), | ||
project_ids: match secret.project_id { | ||
Some(id) => Some(vec![id]), | ||
None => match old_secret.project_id { | ||
Some(id) => Some(vec![id]), | ||
None => bail!("Editing a secret requires a project_id."), | ||
}, | ||
}, | ||
}) | ||
.await?; | ||
serialize_response(new_secret, output_settings); | ||
|
||
Ok(()) | ||
} | ||
|
||
pub(crate) async fn delete(client: Client, secret_ids: Vec<Uuid>) -> Result<()> { | ||
let count = secret_ids.len(); | ||
|
||
let result = client | ||
.secrets() | ||
.delete(SecretsDeleteRequest { ids: secret_ids }) | ||
.await?; | ||
|
||
let secrets_failed: Vec<(Uuid, String)> = result | ||
.data | ||
.into_iter() | ||
.filter_map(|r| r.error.map(|e| (r.id, e))) | ||
.collect(); | ||
let deleted_secrets = count - secrets_failed.len(); | ||
|
||
match deleted_secrets { | ||
2.. => println!("{} secrets deleted successfully.", deleted_secrets), | ||
1 => println!("{} secret deleted successfully.", deleted_secrets), | ||
_ => (), | ||
} | ||
|
||
match secrets_failed.len() { | ||
2.. => eprintln!("{} secrets had errors:", secrets_failed.len()), | ||
1 => eprintln!("{} secret had an error:", secrets_failed.len()), | ||
_ => (), | ||
} | ||
|
||
for secret in &secrets_failed { | ||
eprintln!("{}: {}", secret.0, secret.1); | ||
} | ||
|
||
if !secrets_failed.is_empty() { | ||
bail!("Errors when attempting to delete secrets."); | ||
} | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.