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
39 changes: 36 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions operator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ serde.workspace = true
serde_json.workspace = true
thiserror = "2.0.17"
tokio.workspace = true

[dev-dependencies]
http = "1.1.0"
tower = { version = "0.4.13", features = ["full"] }
2 changes: 2 additions & 0 deletions operator/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ use kube::{
use log::{error, info, warn};

use crds::ConfidentialCluster;
#[cfg(test)]
mod mock_client;
mod reference_values;
mod register_server;
mod trustee;
Expand Down
124 changes: 124 additions & 0 deletions operator/src/mock_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// SPDX-FileCopyrightText: Alice Frosi <afrosi@redhat.com>
// SPDX-FileCopyrightText: Jakob Naucke <jnaucke@redhat.com>
//
// SPDX-License-Identifier: MIT

use http::{Request, Response, StatusCode};
use kube::{Client, client::Body, error::ErrorResponse};
use serde::{Deserialize, Serialize};
use std::convert::Infallible;
use tower::service_fn;

macro_rules! assert_kube_api_error {
($err:expr, $code:expr, $reason:expr, $message:expr, $status:expr) => {{
let kube_error = $err
.downcast_ref::<kube::Error>()
.expect(&format!("Expected kube::Error, got: {:?}", $err));

if let kube::Error::Api(error_response) = kube_error {
assert_eq!(error_response.code, $code);
assert_eq!(error_response.reason, $reason);
assert_eq!(error_response.message, $message);
assert_eq!(error_response.status, $status);
} else {
assert!(false, "Expected kube::Error::Api, got: {:?}", kube_error);
}
}};
}

pub(crate) use assert_kube_api_error;

pub struct MockClient<F, T>
where
F: Fn(&Option<Request<Body>>) -> Result<T, StatusCode> + Send + 'static,
T: Default + Serialize + for<'de> Deserialize<'de>,
{
response_closure: F,
namespace: String,
}

impl<F, T> MockClient<F, T>
where
F: Fn(&Option<Request<Body>>) -> Result<T, StatusCode> + Send + 'static,
T: Clone + Default + Send + Serialize + for<'de> Deserialize<'de> + 'static,
{
pub fn new(response_closure: F, namespace: String) -> Self {
Self {
response_closure,
Comment thread
Jakob-Naucke marked this conversation as resolved.
namespace,
}
}

pub fn into_client(self) -> Client {
let response_data = (self.response_closure)(&None).unwrap_or_default();
let response_json = serde_json::to_string(&response_data).unwrap();
let (kind, name) = serde_json::from_str::<serde_json::Value>(&response_json)
.map(|json_value| {
let kind = json_value
.get("kind")
.and_then(|v| v.as_str())
.unwrap_or("Unknown")
.to_string();
let name = json_value
.get("metadata")
.and_then(|m| m.get("name"))
.and_then(|n| n.as_str())
.unwrap_or("Unknown")
.to_string();
(kind, name)
})
.unwrap_or(("Unknown".to_string(), "Unknown".to_string()));
let plural = kind.to_lowercase() + "s";
let namespace = self.namespace.clone();

let mock_svc = service_fn(move |req: Request<Body>| {
let mut status_code = StatusCode::OK;
let response = (self.response_closure)(&Some(req));
let body = if let Ok(response_data) = response {
let response_json = serde_json::to_string(&response_data).unwrap();
Body::from(response_json.into_bytes())
} else {
status_code = response.err().unwrap();
let code = status_code.as_u16();
let error_response = match status_code {
StatusCode::CONFLICT => ErrorResponse {
status: "Failure".to_string(),
message: format!("{plural} \"{name}\" already exists"),
reason: "AlreadyExists".to_string(),
code,
},
StatusCode::INTERNAL_SERVER_ERROR => ErrorResponse {
status: "Failure".to_string(),
message: "internal server error".to_string(),
reason: "ServerTimeout".to_string(),
code,
},
StatusCode::NOT_FOUND => ErrorResponse {
status: "Failure".to_string(),
message: "resource not found".to_string(),
reason: "NotFound".to_string(),
code,
},
StatusCode::BAD_REQUEST => ErrorResponse {
status: "Failure".to_string(),
message: "bad request".to_string(),
reason: "BadRequest".to_string(),
code,
},
_ => ErrorResponse {
status: "Failure".to_string(),
message: format!("error with status code {status_code}"),
reason: "Unknown".to_string(),
code,
},
};
let error_json = serde_json::to_string(&error_response).unwrap();
Body::from(error_json.into_bytes())
};

let response = Response::builder().status(status_code).body(body).unwrap();
async move { Ok::<_, Infallible>(response) }
});
Client::new(mock_svc, namespace)
}
}
4 changes: 3 additions & 1 deletion operator/src/register_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,10 @@ async fn keygen_reconcile(
machine: Arc<Machine>,
client: Arc<Client>,
) -> Result<Action, ControllerError> {
let client = Arc::unwrap_or_clone(client);
let id = &machine.spec.id;
trustee::generate_secret(Arc::unwrap_or_clone(client), id).await?;
trustee::generate_secret(client.clone(), id).await?;
trustee::mount_secret(client.clone(), id).await?;
Ok(Action::await_change())
}

Expand Down
Loading