Skip to content

Commit

Permalink
feat: Add functionality to delete multiple profiles at once
Browse files Browse the repository at this point in the history
Refs: #9 #15 #16
  • Loading branch information
maikbasel committed May 20, 2024
1 parent e932e46 commit e9d34a4
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 12 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "aws-custodian",
"version": "0.1.0",
"description": "A Next.js front-end application designed to interface with a Rust-powered backend.",
"description": "A Next.js front-end application designed to interface with a Rust-powered Tauri backend.",
"author": {
"name": "Maik Basel"
},
Expand Down
6 changes: 4 additions & 2 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use tauri_plugin_log::LogTarget;

use backend::__cmd__create_profile;
use backend::__cmd__delete_profile;
use backend::__cmd__delete_profiles;
use backend::__cmd__edit_profile;
use backend::__cmd__get_profiles;
use backend::__cmd__validate_credentials;
Expand All @@ -15,7 +16,7 @@ use backend::credentials::core::api::CredentialsDataAPI;
use backend::credentials::core::credentials_service::CredentialsService;
use backend::credentials::infrastructure::aws::sts::sts_adapter::STSAdapter;
use backend::profile::application::tauri::profile_handler::{
create_profile, delete_profile, edit_profile, get_profiles,
create_profile, delete_profile, delete_profiles, edit_profile, get_profiles,
};
use backend::profile::core::api::ProfileDataAPI;
use backend::profile::core::profile_service::ProfileService;
Expand All @@ -40,7 +41,8 @@ fn main() {
create_profile,
edit_profile,
delete_profile,
validate_credentials
validate_credentials,
delete_profiles
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
Expand Down
11 changes: 11 additions & 0 deletions src-tauri/src/profile/application/tauri/profile_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,14 @@ pub async fn delete_profile(
api.delete_profile(&profile_name)
.map_err(ProfileError::from)
}

#[tauri::command]
#[cfg(not(tarpaulin_include))]
pub async fn delete_profiles(
api: tauri::State<'_, Arc<dyn ProfileDataAPI>>,
profile_names: Vec<String>,
) -> Result<(), ProfileError> {
log::info!("delete_profiles: {:?}", profile_names);
api.delete_profiles(profile_names.as_slice())
.map_err(ProfileError::from)
}
2 changes: 2 additions & 0 deletions src-tauri/src/profile/core/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ pub trait ProfileDataAPI: Send + Sync {
fn edit_profile(&self, profile: &Profile) -> Result<(), ProfileError>;

fn delete_profile(&self, profile_name: &str) -> Result<(), ProfileError>;

fn delete_profiles(&self, profile_names: &[String]) -> Result<(), ProfileError>;
}
20 changes: 20 additions & 0 deletions src-tauri/src/profile/core/profile_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ impl ProfileDataAPI for ProfileService {
fn delete_profile(&self, profile_name: &str) -> error_stack::Result<(), ProfileError> {
self.profile_data_spi.remove_profile_data(profile_name)
}

fn delete_profiles(&self, profile_names: &[String]) -> error_stack::Result<(), ProfileError> {
self.profile_data_spi.remove_profiles_data(profile_names)
}
}

#[cfg(test)]
Expand Down Expand Up @@ -115,4 +119,20 @@ mod tests {

assert_that!(actual).is_ok();
}

#[test]
fn should_delete_profiles() {
let profile_names = ["a".to_string(), "b".to_string()];
let mut profile_data_spi_mock = MockProfileDataSPI::new();
profile_data_spi_mock
.expect_remove_profiles_data()
.with(eq(profile_names.clone()))
.times(1)
.returning(move |_| Ok(()));
let profile_service = ProfileService::new(Box::new(profile_data_spi_mock));

let actual = profile_service.delete_profiles(&profile_names);

assert_that!(actual).is_ok();
}
}
2 changes: 2 additions & 0 deletions src-tauri/src/profile/core/spi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ pub trait ProfileDataSPI: Send + Sync {

fn remove_profile_data(&self, profile_name: &str) -> Result<(), ProfileError>;

fn remove_profiles_data(&self, profile_names: &[String]) -> Result<(), ProfileError>;

fn update_profile_data(&self, profile: &Profile) -> Result<(), ProfileError>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,19 @@ impl ProfileDataSPI for SdkConfigAdapter {
Ok(())
}

fn remove_profiles_data(
&self,
profile_names: &[String],
) -> error_stack::Result<(), ProfileError> {
// Might need some optimization in the future.
for profile_name in profile_names {
Self::delete_from_config(profile_name)?;
Self::delete_from_credentials_file(profile_name)?;
}

Ok(())
}

fn update_profile_data(
&self,
profile: &DomainProfile,
Expand Down Expand Up @@ -112,8 +125,7 @@ impl SdkConfigAdapter {

fn delete_from_credentials_file(profile_name: &str) -> error_stack::Result<(), ProfileError> {
let credentials_file_location = Self::get_credentials_file_location()?;
let mut config_file = Ini::load_from_file(&credentials_file_location)
.change_context(ProfileError::CredentialsFileLoadError)?;
let mut config_file = Self::load_credentials_file(&credentials_file_location)?;

config_file.delete(Some(profile_name));

Expand All @@ -125,8 +137,7 @@ impl SdkConfigAdapter {

fn delete_from_config(profile_name: &str) -> error_stack::Result<(), ProfileError> {
let config_file_location = Self::get_config_file_location()?;
let mut config_file = Ini::load_from_file(&config_file_location)
.change_context(ProfileError::ConfigFileLoadError)?;
let mut config_file = Self::load_config_file(&config_file_location)?;

config_file.delete(Some(format!("profile {}", profile_name)));

Expand All @@ -136,6 +147,17 @@ impl SdkConfigAdapter {
Ok(())
}

fn load_credentials_file(
credentials_file_location: &String,
) -> error_stack::Result<Ini, ProfileError> {
Ini::load_from_file(credentials_file_location)
.change_context(ProfileError::CredentialsFileLoadError)
}

fn load_config_file(config_file_location: &String) -> error_stack::Result<Ini, ProfileError> {
Ini::load_from_file(config_file_location).change_context(ProfileError::ConfigFileLoadError)
}

fn create_profile_in_config_file(
profile: &DomainProfile,
) -> error_stack::Result<(), ProfileError> {
Expand Down
54 changes: 54 additions & 0 deletions src-tauri/tests/sdk_config_adapter_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ mod tests {
.with_section(Some("profile dev"))
.set("region", "eu-west-1")
.set("output", "json");
test_config
.with_section(Some("profile qa"))
.set("region", "eu-east-1")
.set("output", "table");
test_config
.with_section(Some("profile prod"))
.set("region", "eu-east-2")
.set("output", "json");
let test_config_file_path = test_aws_dir_path.join("config");
test_config.write_to_file(&test_config_file_path).unwrap();
env::set_var("AWS_CONFIG_FILE", test_config_file_path);
Expand All @@ -59,6 +67,14 @@ mod tests {
.with_section(Some("dev"))
.set("aws_access_key_id", "devAccessKeyID")
.set("aws_secret_access_key", "devSecretAccessKey");
test_credentials
.with_section(Some("qa"))
.set("aws_access_key_id", "qaAccessKeyID")
.set("aws_secret_access_key", "qaSecretAccessKey");
test_credentials
.with_section(Some("prod"))
.set("aws_access_key_id", "prodAccessKeyID")
.set("aws_secret_access_key", "prodSecretAccessKey");
let test_credentials_file_path = test_aws_dir_path.join("credentials");
test_credentials
.write_to_file(&test_credentials_file_path)
Expand Down Expand Up @@ -189,6 +205,44 @@ mod tests {
assert_that(&actual_credentials.section(Some("dev"))).is_none();
}

#[test_context(ValidContext)]
#[test]
#[serial]
fn should_remove_config_for_given_profiles(_: &mut ValidContext) {
let config_file_location = env::var("AWS_CONFIG_FILE").ok().unwrap();
let cut: Box<dyn ProfileDataSPI> = Box::new(SdkConfigAdapter);
let given_config = Ini::load_from_file(config_file_location.clone()).unwrap();
assert_that(&given_config.section(Some("profile dev"))).is_some();
assert_that(&given_config.section(Some("profile qa"))).is_some();
let input = ["dev".to_string(), "qa".to_string()];

let result = cut.remove_profiles_data(&input);

assert_that(&result).is_ok();
let actual_config = Ini::load_from_file(config_file_location).unwrap();
assert_that(&actual_config.section(Some("profile dev"))).is_none();
assert_that(&actual_config.section(Some("profile qa"))).is_none();
}

#[test_context(ValidContext)]
#[test]
#[serial]
fn should_remove_credentials_for_given_profiles(_: &mut ValidContext) {
let credentials_file_location = env::var("AWS_SHARED_CREDENTIALS_FILE").ok().unwrap();
let cut: Box<dyn ProfileDataSPI> = Box::new(SdkConfigAdapter);
let given_credentials = Ini::load_from_file(credentials_file_location.clone()).unwrap();
assert_that(&given_credentials.section(Some("dev"))).is_some();
assert_that(&given_credentials.section(Some("qa"))).is_some();
let input = ["dev".to_string(), "qa".to_string()];

let result = cut.remove_profiles_data(&input);

assert_that(&result).is_ok();
let actual_credentials = Ini::load_from_file(credentials_file_location).unwrap();
assert_that(&actual_credentials.section(Some("dev"))).is_none();
assert_that(&actual_credentials.section(Some("qa"))).is_none();
}

#[test_context(ValidContext)]
#[test]
#[serial]
Expand Down
14 changes: 9 additions & 5 deletions src/sections/profiles/components/profile-actions-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
} from '@/components/ui/alert-dialog';
import ProfileFormDialog from '@/sections/profiles/components/profile-form-dialog';
import { Profile } from '@/modules/profiles/domain';
import { invoke } from '@tauri-apps/api/tauri';
import { mutate } from 'swr';

interface DataTableActionsButtonProps {
selectedRows: Profile[];
Expand All @@ -35,11 +37,13 @@ export default function ProfileActionsButton({
const [showCreateDialog, setShowCreateDialog] = React.useState(false);

async function onDelete() {
console.info('deleted', selectedRows);
// invoke('delete_profile', { profileName: profile.name }).then(() => {
// mutate('get_profiles');
// });
// setShowDeleteDialog(false);
console.info('deleted1', selectedRows);
const profileNames = selectedRows.map((row) => row.name);
console.info('deleted2', profileNames);
invoke('delete_profiles', { profileNames: profileNames }).then(() => {
mutate('get_profiles');
});
setShowDeleteDialog(false);
}

return (
Expand Down

0 comments on commit e9d34a4

Please sign in to comment.