Arnavion / k8s-openapi

Rust definitions of the resource types in the Kubernetes client API
Apache License 2.0
379 stars 41 forks source link

CRD derive cannot associate subresources #62

Closed clux closed 4 years ago

clux commented 4 years ago

Hello again! Trying out the very nifty k8s-openapi-derive crate now :-)

Currently the #[derive(CustomResourceDefinition)] expects a FooSpec struct to then attach inside a Foo struct. But AFAIK, there's no way to attach a FooStatus struct, so we could access Foo::status fields.

E.g.

#[derive(CustomResourceDefinition, Deserialize, Serialize, Clone, Debug, PartialEq)]
#[custom_resource_definition(group = "clux.dev", version = "v1", plural = "foos", namespaced)]
pub struct FooSpec {
    name: String,
    info: String,
}

#[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub struct FooStatus { // HOW TO ASSOCIATE WITH GENERATED Foo
    is_bad: bool,
}

This would also require the subresources (in the crd spec) enabled to work, but can't get far enough for that to matter atm.

        "metadata": {
            "name": "foos.clux.dev"
        },
        "spec": {
            "group": "clux.dev",
            "version": "v1",
            "scope": "Namespaced",
            "names": {
                "plural": "foos",
                "singular": "foo",
                "kind": "Foo",
            },
            "subresources": {
                "status": {},
            }
clux commented 4 years ago

For how to customise the crd, kubebuilder does this type of thing: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html - which might be applicable. But I'm not sure if you want to go that deep with it (or that string typed).

Arnavion commented 4 years ago

(Yeah, I haven't kept up with CRDs after the initial v1beta1. I should update the test to use v1 too...)

CustomResourceSubresources is already defined in the crate, so it could be enough to just add the equivalent of #[serde(flatten)] sub_resources: CustomResourceSubresources to every generated struct Foo. If you need the status, you can deserialize it manually from the CustomResourceSubresourceStatus's inner serde_json::Value using its Deserializer impl.

// Generated
struct Foo {
    spec: Option<FooSpec>,
    sub_resources: k8s_openapi::...::CustomResourceSubresources,
}

// Generated
impl Deserialize for Foo {
   ... // Foo::sub_resources's fields will be set to Some(...) if they're in the JSON
}

// User's own type
struct FooStatus {
    ...
}

let foo: Foo = ...;
let status: Option<FooStatus> = foo.sub_resources.status.map(|status| serde::Deserialize::deserialize(status)).transpose().unwrap();
Arnavion commented 4 years ago

I have a WIP implementation in https://github.com/Arnavion/k8s-openapi/compare/fix-62 It still has some work to be done, but let me know if it works for you. See the custom_resource_definition.rs test for an example.

TODO:

clux commented 4 years ago

Nice! I was goofing around with your library last night but am not great with proc_macro debugging. Made the following test case for it, not sure if it's meant to work like this:

#[macro_use] extern crate serde_derive;
#[macro_use] extern crate k8s_openapi_derive;
use k8s_openapi::Resource;

#[derive(CustomResourceDefinition, Deserialize, Serialize, Clone, Debug, PartialEq)]
#[custom_resource_definition(group = "clux.dev", version = "v1", plural = "foos", namespaced, has_subresources = "v1beta1")]
pub struct FooSpec {
    name: String,
    info: String,
}

#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
pub struct FooStatus {
    is_bad: bool,
}

fn main()  {
    println!("Kind {}", Foo::KIND);
    let mut foo = Foo::default();
    foo.status = Some(FooStatus { is_bad: true });
}

ultimately fails whenever i set the new arg to anything. posted an error on the commit.

clux commented 4 years ago

One tangential suggestion; you can omit the plural arg. You can get the plural value via to_plural(K::KIND.to_ascii_lowercase()) via inflector::string::pluralize.

We rely on this in kube already because that information is not passed through the k8s_openapi::Resource trait.

clux commented 4 years ago

Tested with this diff:

diff --git a/kube/Cargo.toml b/kube/Cargo.toml
index a562f98..6bb3500 100644
--- a/kube/Cargo.toml
+++ b/kube/Cargo.toml
@@ -42,6 +42,8 @@ features = ["json", "gzip", "stream"]

 [dependencies.k8s-openapi]
 version = "0.7.1"
+git = "https://github.com/Arnavion/k8s-openapi"
+rev = "cf1062a5def77f111179c40a8d73424e66f95aa3"
 default-features = false
 features = []

@@ -55,9 +57,16 @@ tempfile = "3.1.0"
 env_logger = "0.7.1"
 tokio = { version = "0.2.11", features = ["full"] }
 anyhow = "1.0.26"
-k8s-openapi-derive = "0.7.1"
+k8s-openapi-derive = { git = "https://github.com/Arnavion/k8s-openapi", rev = "cf1062a5def77f111179c40a8d73424e66f95aa3" }

 [dev-dependencies.k8s-openapi]
-version = "0.7.1"
+#version = "0.7.1"
+git = "https://github.com/Arnavion/k8s-openapi"
+rev = "cf1062a5def77f111179c40a8d73424e66f95aa3"
 default-features = false
 features = ["v1_17"]
+
+[dev-dependencies.k8s-openapi-codegen-common]
+#version = "0.7.1"
+git = "https://github.com/Arnavion/k8s-openapi"
+rev = "cf1062a5def77f111179c40a8d73424e66f95aa3"
diff --git a/kube/examples/crd_derive.rs b/kube/examples/crd_derive.rs
new file mode 100644
index 0000000..8883fe4
--- /dev/null
+++ b/kube/examples/crd_derive.rs
@@ -0,0 +1,23 @@
+#[macro_use] extern crate serde_derive;
+#[macro_use] extern crate k8s_openapi_derive;
+use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::CustomResourceSubresourceStatus;
+use k8s_openapi::Resource;
+
+#[derive(CustomResourceDefinition, Deserialize, Serialize, Clone, Debug, PartialEq)]
+#[custom_resource_definition(group = "clux.dev", version = "v1", plural = "foos", namespaced, has_subresources = "v1")]
+pub struct FooSpec {
+    name: String,
+    info: String,
+}
+
+#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
+pub struct FooStatus {
+    is_bad: bool,
+}
+
+fn main()  {
+    println!("Kind {}", Foo::KIND);
+    let mut foo = Foo::default();
+    //foo.subresources.status = Some(CustomResourceSubresourceStatus(serde_json::Value));
+    foo.status = Some(FooStatus { is_bad: true });
+}

So it does something with status now. It constructs the foo.subresources.status field, which appear to be an Option<CustomResourceSubresourceStatus<Value>> which isn't super helpful.

The raw foo.status does not exist. I don't think you can use flatten straight on the hidden root object, that causes the unexpected nesting for users.

Arnavion commented 4 years ago

Read https://github.com/Arnavion/k8s-openapi/issues/62#issuecomment-593136647

clux commented 4 years ago

Ok, sorry, this works:

    let status = serde_json::to_value(FooStatus { is_bad: true }).unwrap();
    foo.subresources.status = Some(CustomResourceSubresourceStatus(status));
    println!("Foo: {:?}", foo)

which creates this output:

Kind Foo
Foo: Foo { metadata: None, spec: None, subresources: CustomResourceSubresources { scale: None, status: Some(CustomResourceSubresourceStatus(Object({"is_bad": Bool(true)}))) } }
Arnavion commented 4 years ago

Yes, and then if you serialize foo to JSON you'll see the fields of subresources getting flattened into the main object, like they should be.