Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
File renamed without changes.
19 changes: 13 additions & 6 deletions src/resource/backends/sql.rs → src/resource/backend/sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ use super::super::types::*;
use crate::config::Config;
use crate::keystone::ServiceState;
use crate::resource::ResourceProviderError;
use crate::resource::backends::sql::domain::{get_domain_by_id, get_domain_by_name};
use crate::resource::backends::sql::project::{get_project, get_project_by_name};

#[derive(Clone, Debug, Default)]
pub struct SqlBackend {
Expand All @@ -44,7 +42,7 @@ impl ResourceBackend for SqlBackend {
state: &ServiceState,
domain_id: &'a str,
) -> Result<Option<Domain>, ResourceProviderError> {
Ok(get_domain_by_id(&self.config, &state.db, domain_id).await?)
Ok(domain::get_domain_by_id(&self.config, &state.db, domain_id).await?)
}

/// Get single domain by Name
Expand All @@ -53,7 +51,7 @@ impl ResourceBackend for SqlBackend {
state: &ServiceState,
domain_name: &'a str,
) -> Result<Option<Domain>, ResourceProviderError> {
Ok(get_domain_by_name(&self.config, &state.db, domain_name).await?)
Ok(domain::get_domain_by_name(&self.config, &state.db, domain_name).await?)
}

/// Get single project by ID
Expand All @@ -62,7 +60,7 @@ impl ResourceBackend for SqlBackend {
state: &ServiceState,
project_id: &'a str,
) -> Result<Option<Project>, ResourceProviderError> {
Ok(get_project(&self.config, &state.db, project_id).await?)
Ok(project::get_project(&self.config, &state.db, project_id).await?)
}

/// Get single project by Name and Domain ID
Expand All @@ -72,7 +70,16 @@ impl ResourceBackend for SqlBackend {
name: &'a str,
domain_id: &'a str,
) -> Result<Option<Project>, ResourceProviderError> {
Ok(get_project_by_name(&self.config, &state.db, name, domain_id).await?)
Ok(project::get_project_by_name(&self.config, &state.db, name, domain_id).await?)
}

/// Get project parents
async fn get_project_parents<'a>(
&self,
state: &ServiceState,
project_id: &'a str,
) -> Result<Option<Vec<Project>>, ResourceProviderError> {
Ok(project::get_project_parents(&state.db, project_id).await?)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use sea_orm::query::*;

use crate::db::entity::{prelude::Project as DbProject, project as db_project};
use crate::resource::Config;
use crate::resource::backends::error::{ResourceDatabaseError, db_err};
use crate::resource::backend::error::{ResourceDatabaseError, db_err};
use crate::resource::types::Domain;

pub async fn get_domain_by_id<I: AsRef<str>>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub use get::get_domain_by_id;
pub use get::get_domain_by_name;

use crate::db::entity::project as db_project;
use crate::resource::backends::error::ResourceDatabaseError;
use crate::resource::backend::error::ResourceDatabaseError;
use crate::resource::types::Domain;
use crate::resource::types::DomainBuilder;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use sea_orm::query::*;

use crate::db::entity::{prelude::Project as DbProject, project as db_project};
use crate::resource::Config;
use crate::resource::backends::error::{ResourceDatabaseError, db_err};
use crate::resource::backend::error::{ResourceDatabaseError, db_err};
use crate::resource::types::Project;

pub async fn get_project<I: AsRef<str>>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ use serde_json::Value;
use tracing::error;

mod get;
mod tree;

pub use get::get_project;
pub use get::get_project_by_name;
pub use tree::get_project_parents;

use crate::db::entity::project as db_project;
use crate::resource::backends::error::ResourceDatabaseError;
use crate::resource::backend::error::ResourceDatabaseError;
use crate::resource::types::Project;
use crate::resource::types::ProjectBuilder;

Expand All @@ -30,6 +32,9 @@ impl TryFrom<db_project::Model> for Project {
fn try_from(value: db_project::Model) -> Result<Self, Self::Error> {
let mut project_builder = ProjectBuilder::default();
project_builder.id(value.id.clone());
if let Some(parent_id) = &value.parent_id {
project_builder.parent_id(parent_id);
}
project_builder.name(value.name.clone());
project_builder.domain_id(value.domain_id.clone());
if let Some(description) = &value.description {
Expand All @@ -39,7 +44,12 @@ impl TryFrom<db_project::Model> for Project {
if let Some(extra) = &value.extra {
project_builder.extra(
serde_json::from_str::<Value>(extra)
.inspect_err(|e| error!("failed to deserialize project extra properties: {e}"))
.inspect_err(|e| {
error!(
"failed to deserialize project [id: {}] extra properties: {e}",
value.id
)
})
.unwrap_or_default(),
);
}
Expand Down
140 changes: 140 additions & 0 deletions src/resource/backend/sql/project/tree.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

use sea_orm::DatabaseConnection;
use sea_orm::entity::*;

use crate::db::entity::prelude::Project as DbProject;
use crate::resource::backend::error::{ResourceDatabaseError, db_err};
use crate::resource::types::Project;

pub async fn get_project_parents<I: AsRef<str>>(
db: &DatabaseConnection,
id: I,
) -> Result<Option<Vec<Project>>, ResourceDatabaseError> {
let mut res: Vec<Project> = Vec::new();
let mut project_id: Option<String> = Some(id.as_ref().to_string());
while let Some(pid) = project_id {
let project = DbProject::find_by_id(pid.clone())
.one(db)
.await
.map_err(|err| db_err(err, "resolving project parents"))?;
if pid == id.as_ref() && project.is_none() {
return Ok(None);
}
if let Some(p) = &project
&& pid != id.as_ref()
{
res.push(p.clone().try_into()?);
}
project_id = project.and_then(|project| project.parent_id);
}

Ok(Some(res))
}

#[cfg(test)]
mod tests {
use sea_orm::{DatabaseBackend, MockDatabase, Transaction};

use crate::db::entity::project;

use super::*;
use crate::resource::types::Project;

pub(super) fn get_mock<I: AsRef<str>, P: AsRef<str>>(
id: I,
parent_id: Option<P>,
) -> project::Model {
project::Model {
id: id.as_ref().to_string(),
name: "project_name".into(),
extra: None,
description: Some("description".into()),
enabled: Some(true),
domain_id: "domain_id".into(),
parent_id: parent_id.as_ref().map(|val| val.as_ref().into()),
is_domain: parent_id.as_ref().is_some(),
}
}

#[tokio::test]
async fn test_get_project_parents() {
// Create MockDatabase with mock query results
let db = MockDatabase::new(DatabaseBackend::Postgres)
.append_query_results([vec![get_mock("3", Some("2"))]])
.append_query_results([vec![get_mock("2", Some("1"))]])
.append_query_results([vec![get_mock("1", Some("domain_id"))]])
.append_query_results([vec![get_mock("domain_id", None::<&str>)]])
.into_connection();
assert_eq!(
get_project_parents(&db, "3").await.unwrap().unwrap(),
vec![
Project {
id: "2".into(),
parent_id: Some("1".into()),
name: "project_name".into(),
domain_id: "domain_id".into(),
enabled: true,
description: Some("description".into()),
extra: None
},
Project {
id: "1".into(),
parent_id: Some("domain_id".into()),
name: "project_name".into(),
domain_id: "domain_id".into(),
enabled: true,
description: Some("description".into()),
extra: None
},
Project {
id: "domain_id".into(),
parent_id: None,
name: "project_name".into(),
domain_id: "domain_id".into(),
enabled: true,
description: Some("description".into()),
extra: None
}
]
);
// Checking transaction log
assert_eq!(
db.into_transaction_log(),
[
Transaction::from_sql_and_values(
DatabaseBackend::Postgres,
r#"SELECT "project"."id", "project"."name", "project"."extra", "project"."description", "project"."enabled", "project"."domain_id", "project"."parent_id", "project"."is_domain" FROM "project" WHERE "project"."id" = $1 LIMIT $2"#,
["3".into(), 1u64.into()]
),
Transaction::from_sql_and_values(
DatabaseBackend::Postgres,
r#"SELECT "project"."id", "project"."name", "project"."extra", "project"."description", "project"."enabled", "project"."domain_id", "project"."parent_id", "project"."is_domain" FROM "project" WHERE "project"."id" = $1 LIMIT $2"#,
["2".into(), 1u64.into()]
),
Transaction::from_sql_and_values(
DatabaseBackend::Postgres,
r#"SELECT "project"."id", "project"."name", "project"."extra", "project"."description", "project"."enabled", "project"."domain_id", "project"."parent_id", "project"."is_domain" FROM "project" WHERE "project"."id" = $1 LIMIT $2"#,
["1".into(), 1u64.into()]
),
Transaction::from_sql_and_values(
DatabaseBackend::Postgres,
r#"SELECT "project"."id", "project"."name", "project"."extra", "project"."description", "project"."enabled", "project"."domain_id", "project"."parent_id", "project"."is_domain" FROM "project" WHERE "project"."id" = $1 LIMIT $2"#,
["domain_id".into(), 1u64.into()]
),
]
);
}
}
2 changes: 1 addition & 1 deletion src/resource/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

use thiserror::Error;

use crate::resource::backends::error::*;
use crate::resource::backend::error::*;
use crate::resource::types::DomainBuilderError;

#[derive(Error, Debug)]
Expand Down
27 changes: 25 additions & 2 deletions src/resource/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ use async_trait::async_trait;
#[cfg(test)]
use mockall::mock;

pub mod backends;
pub mod backend;
pub mod error;
pub(crate) mod types;

use crate::config::Config;
use crate::keystone::ServiceState;
use crate::plugin_manager::PluginManager;
use crate::resource::backends::sql::SqlBackend;
use crate::resource::backend::sql::SqlBackend;
use crate::resource::error::ResourceProviderError;
use crate::resource::types::{Domain, Project, ResourceBackend};

Expand Down Expand Up @@ -58,6 +58,13 @@ pub trait ResourceApi: Send + Sync + Clone {
name: &'a str,
domain_id: &'a str,
) -> Result<Option<Project>, ResourceProviderError>;

/// Get project parents
async fn get_project_parents<'a>(
&self,
state: &ServiceState,
project_id: &'a str,
) -> Result<Option<Vec<Project>>, ResourceProviderError>;
}

#[cfg(test)]
Expand Down Expand Up @@ -93,6 +100,11 @@ mock! {
domain_id: &'a str,
) -> Result<Option<Project>, ResourceProviderError>;

async fn get_project_parents<'a>(
&self,
state: &ServiceState,
project_id: &'a str,
) -> Result<Option<Vec<Project>>, ResourceProviderError>;
}

impl Clone for ResourceProvider {
Expand Down Expand Up @@ -170,4 +182,15 @@ impl ResourceApi for ResourceProvider {
.get_project_by_name(state, name, domain_id)
.await
}

/// Get project parents
async fn get_project_parents<'a>(
&self,
state: &ServiceState,
project_id: &'a str,
) -> Result<Option<Vec<Project>>, ResourceProviderError> {
self.backend_driver
.get_project_parents(state, project_id)
.await
}
}
7 changes: 7 additions & 0 deletions src/resource/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ pub trait ResourceBackend: DynClone + Send + Sync + std::fmt::Debug {
name: &'a str,
domain_id: &'a str,
) -> Result<Option<Project>, ResourceProviderError>;

/// Get project parents
async fn get_project_parents<'a>(
&self,
state: &ServiceState,
project_id: &'a str,
) -> Result<Option<Vec<Project>>, ResourceProviderError>;
}

dyn_clone::clone_trait_object!(ResourceBackend);
8 changes: 6 additions & 2 deletions src/resource/types/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,19 @@ use serde_json::Value;
pub struct Project {
/// The project ID.
pub id: String,
/// The ID of the parent for the project.
#[builder(default)]
pub parent_id: Option<String>,
/// The project name.
pub name: String,
/// The project domain_id.
pub domain_id: String,
/// If set to true, project is enabled. If set to false, project is disabled.
pub enabled: bool,
/// The resource description
/// The description of the project.
#[builder(default)]
pub description: Option<String>,
/// Additional project properties
/// Additional project properties.
#[builder(default)]
pub extra: Option<Value>,
}
Loading