#[cfg(not(feature = "_docsrs"))]
pub mod binding {
#![allow(warnings, errors)]
rust2go::r2g_include_binding!();
}
#[cfg(not(feature = "_docsrs"))]
#[rust2go::r2g]
trait EnvTest {
fn create(req: Environment) -> CreateResponse;
fn destroy(kubeconfig: String) -> DestroyResponse;
}
#[derive(Default)]
#[cfg_attr(not(feature = "_docsrs"), derive(rust2go::R2G))]
struct CreateResponse {
err: Option<String>,
error_type: Option<u8>,
server: Server,
}
#[derive(Default)]
#[cfg_attr(not(feature = "_docsrs"), derive(rust2go::R2G))]
struct DestroyResponse {
err: Option<String>,
error_type: Option<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
enum CreateErrorType {
SetupBinaryAssetsDirectory = 0,
DecodeCrd = 1,
StartEnvironment = 2,
BuildKubeconfig = 3,
StopEnvironment = 4,
#[default]
Generic = 5,
}
impl From<u8> for CreateErrorType {
fn from(value: u8) -> Self {
match value {
0 => Self::SetupBinaryAssetsDirectory,
1 => Self::DecodeCrd,
2 => Self::StartEnvironment,
3 => Self::BuildKubeconfig,
4 => Self::StopEnvironment,
_ => Self::default(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
enum DestroyErrorType {
EnvMissing = 0,
#[default]
StopEnvironment = 1,
}
impl From<u8> for DestroyErrorType {
fn from(value: u8) -> Self {
match value {
0 => Self::EnvMissing,
_ => Self::default(),
}
}
}
#[derive(thiserror::Error, Debug)]
pub enum EnvironmentError {
#[error("Environment create error: {0}")]
Create(String),
#[error("Setup binary assets directory error: {0}")]
SetupBinaryAssetsDirectory(String),
#[error("CRD decode error: {0}")]
DecodeCrd(String),
#[error("Start environment error: {0}")]
StartEnvironment(String),
#[error("Build kubeconfig error: {0}")]
BuildKubeconfig(String),
#[error("Stop environment error: {0}")]
StopEnvironment(String),
#[error("CRD serialization error: {0}")]
CrdSerialize(#[from] serde_json::Error),
#[error("Unsupported CRD type. Expected object, or array of those.")]
UnsupportedCrdType,
}
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(not(feature = "_docsrs"), derive(rust2go::R2G))]
pub struct Environment {
pub crd_install_options: CRDInstallOptions,
pub binary_assets_settings: BinaryAssetsSettings,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(not(feature = "_docsrs"), derive(rust2go::R2G))]
pub struct BinaryAssetsSettings {
pub download_binary_assets: bool,
pub download_binary_assets_version: Option<String>,
pub download_binary_assets_index_url: Option<String>,
pub binary_assets_directory: Option<String>,
}
impl Default for BinaryAssetsSettings {
fn default() -> Self {
Self {
download_binary_assets: true,
download_binary_assets_version: Option::default(),
download_binary_assets_index_url: Option::default(),
binary_assets_directory: Option::default(),
}
}
}
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(not(feature = "_docsrs"), derive(rust2go::R2G))]
pub struct CRDInstallOptions {
paths: Vec<String>,
crds: Vec<String>,
error_if_path_missing: bool,
}
impl Environment {
pub fn create(&self) -> Result<Server, EnvironmentError> {
#[cfg(feature = "_docsrs")]
let res = CreateResponse::default();
#[cfg(not(feature = "_docsrs"))]
let res = EnvTestImpl::create(self.clone());
if let Some(err) = res.err {
let err = match res.error_type.map(Into::into).unwrap_or_default() {
CreateErrorType::SetupBinaryAssetsDirectory => {
EnvironmentError::SetupBinaryAssetsDirectory(err)
}
CreateErrorType::DecodeCrd => EnvironmentError::DecodeCrd(err),
CreateErrorType::StartEnvironment => EnvironmentError::StartEnvironment(err),
CreateErrorType::BuildKubeconfig => EnvironmentError::BuildKubeconfig(err),
CreateErrorType::StopEnvironment => EnvironmentError::StopEnvironment(err),
CreateErrorType::Generic => EnvironmentError::Create(err),
};
return Err(err);
}
Ok(res.server)
}
pub fn with_crds(mut self, crds: impl serde::Serialize) -> Result<Self, EnvironmentError> {
match serde_json::to_value(crds)? {
serde_json::Value::Array(crds) => {
for crd in crds {
self.crd_install_options
.crds
.push(serde_json::to_string(&crd)?);
}
}
serde_json::Value::Object(crd) => {
self.crd_install_options
.crds
.push(serde_json::to_string(&crd)?);
}
_ => return Err(EnvironmentError::UnsupportedCrdType),
}
Ok(self)
}
}
#[derive(thiserror::Error, Debug)]
pub enum ServerError {
#[error("Environment destroy error: {0}")]
Destroy(String),
#[error("Stop environment error: {0}")]
StopEnvironment(String),
#[error("Stop environment error - missing for the provided kubeconfig: {0}")]
EnvironmentMissing(String),
#[error("Deserialize kubeconfig error: {0}")]
Kubeconfig(#[from] serde_json::Error),
#[cfg(feature = "kube")]
#[error("Opening client error: {0}")]
Client(#[from] kube::Error),
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(not(feature = "_docsrs"), derive(rust2go::R2G))]
pub struct Server {
kubeconfig: String,
}
impl Server {
pub fn destroy(&self) -> Result<(), ServerError> {
#[cfg(feature = "_docsrs")]
let res = DestroyResponse::default();
#[cfg(not(feature = "_docsrs"))]
let res = EnvTestImpl::destroy(self.kubeconfig.clone());
if let Some(err) = res.err {
let err = match res.error_type.map(Into::into).unwrap_or_default() {
DestroyErrorType::StopEnvironment => ServerError::StopEnvironment(err),
DestroyErrorType::EnvMissing => ServerError::EnvironmentMissing(err),
};
return Err(err);
}
Ok(())
}
#[cfg(feature = "kube")]
#[inline]
pub fn client(&self) -> Result<kube::Client, ServerError> {
Ok(self.kubeconfig()?.try_into()?)
}
#[cfg(feature = "kube")]
#[inline]
pub fn kubeconfig(&self) -> Result<kube::config::Kubeconfig, serde_json::Error> {
serde_json::from_str(self.as_ref())
}
}
impl AsRef<str> for Server {
fn as_ref(&self) -> &str {
&self.kubeconfig
}
}
impl Drop for Server {
fn drop(&mut self) {
let _ = self.destroy();
}
}
#[cfg(test)]
mod tests {
use super::Environment;
#[tokio::test]
async fn e2e() {
let env = Environment::default();
let server = env.create().unwrap();
server.destroy().unwrap();
}
#[cfg(feature = "kube")]
#[tokio::test]
async fn with_crds_accepts_simple_real_crd() {
let crd = serde_json::json!({
"apiVersion": "apiextensions.k8s.io/v1",
"kind": "CustomResourceDefinition",
"metadata": { "name": "widgets.example.com" },
"spec": {
"group": "example.com",
"scope": "Namespaced",
"names": {
"plural": "widgets",
"singular": "widget",
"kind": "Widget",
"listKind": "WidgetList"
},
"versions": [{
"name": "v1",
"served": true,
"storage": true,
"schema": {
"openAPIV3Schema": {
"type": "object"
}
}
}]
}
});
let env = Environment::default().with_crds(crd.clone()).unwrap();
assert_eq!(env.crd_install_options.crds, vec![crd.to_string()]);
let server = env.create().unwrap();
let client = server.client().unwrap();
let groups = client.list_api_groups().await.unwrap();
groups
.groups
.iter()
.find(|g| g.name == "example.com")
.ok_or(())
.unwrap();
}
#[test]
fn with_crds_accepts_option_single_vec_and_slice() {
let crd_a =
serde_json::json!({"kind": "CustomResourceDefinition", "metadata": {"name": "a"}});
let crd_b =
serde_json::json!({"kind": "CustomResourceDefinition", "metadata": {"name": "b"}});
let crd_c =
serde_json::json!({"kind": "CustomResourceDefinition", "metadata": {"name": "c"}});
let env_single = Environment::default().with_crds(crd_a.clone()).unwrap();
assert_eq!(env_single.crd_install_options.crds, vec![crd_a.to_string()]);
let env_option = Environment::default()
.with_crds(Some(crd_a.clone()))
.unwrap();
assert_eq!(env_option.crd_install_options.crds, vec![crd_a.to_string()]);
let env_vec = Environment::default()
.with_crds(vec![crd_a.clone(), crd_b.clone()])
.unwrap();
assert_eq!(
env_vec.crd_install_options.crds,
vec![crd_a.to_string(), crd_b.to_string()]
);
let crds = [crd_a.clone(), crd_b.clone(), crd_c.clone()];
let env_slice = Environment::default().with_crds(&crds).unwrap();
assert_eq!(
env_slice.crd_install_options.crds,
vec![crd_a.to_string(), crd_b.to_string(), crd_c.to_string()]
);
}
}