Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate rust bindings from Kubernetes openapi #4

Open
sehz opened this issue Jun 5, 2020 · 18 comments
Open

Generate rust bindings from Kubernetes openapi #4

sehz opened this issue Jun 5, 2020 · 18 comments
Assignees

Comments

@sehz
Copy link
Collaborator

sehz commented Jun 5, 2020

Kubernetes uses OpenAPI spec: https://github.com/kubernetes/kubernetes/tree/master/api/openapi-spec to define their api.

Generate rust k8-api bindings from the spec which implements Spec trait as in the example:

const SERVICE_API: Crd = Crd {
    group: "core",
    version: "v1",
    names: CrdNames {
        kind: "Service",
        plural: "services",
        singular: "service",
    },
};

#[derive(Deserialize, Serialize, Debug, PartialEq, Default, Clone)]
#[serde(rename_all = "camelCase",default)]
pub struct ServiceSpec {
    #[serde(rename = "clusterIP")]
    pub cluster_ip: String,
    #[serde(rename = "externalIPs")]
    pub external_ips: Vec<String>,
    #[serde(rename = "loadBalancerIP")]
    pub load_balancer_ip: Option<String>,
    pub r#type: Option<LoadBalancerType>,
    pub external_name: Option<String>,
    pub external_traffic_policy: Option<ExternalTrafficPolicy>,
    pub ports: Vec<ServicePort>,
    pub selector: Option<HashMap<String, String>>,
}

impl Spec for ServiceSpec {

    type Status = ServiceStatus;
    type Header = DefaultHeader;

    fn metadata() -> &'static Crd {
        &SERVICE_API
    }

@sehz
Copy link
Collaborator Author

sehz commented Nov 12, 2021

@qrilka Interested in working on this?

@qrilka
Copy link
Contributor

qrilka commented Nov 12, 2021

I need to take a better look to understand the details but could it help with #92 and #112 ? Is the idea to generate code with a macro or maybe somehow else?

@qrilka
Copy link
Contributor

qrilka commented Nov 12, 2021

@vijaylaxmid did you have any progress with this? Maybe some ideas?

@sehz
Copy link
Collaborator Author

sehz commented Nov 13, 2021

Yes, using procedure macro to generate would make sense. Something like:

#[kubeapi(group = "core",version ="1", status = ServiceStatus)]
pub struct ServiceSpec {
    #[serde(rename = "clusterIP")]
    pub cluster_ip: String,
    #[serde(rename = "externalIPs")]
    pub external_ips: Vec<String>,
    #[serde(rename = "loadBalancerIP")]
    pub load_balancer_ip: Option<String>,
    pub r#type: Option<LoadBalancerType>,
    pub external_name: Option<String>,
    pub external_traffic_policy: Option<ExternalTrafficPolicy>,
    pub ports: Vec<ServicePort>,
    pub selector: Option<HashMap<String, String>>,
}

in the kube-rs, similar derive: https://docs.rs/kube-derive/0.63.2/kube_derive/derive.CustomResource.html

@qrilka
Copy link
Contributor

qrilka commented Nov 15, 2021

I don't have much experience with macros yes but the task looks interesting and looks helpful for infinyon/fluvio#1131 that I was originally looking into when starting with k8-api/fluvio
Not sure how much time this will take though :)

@qrilka
Copy link
Contributor

qrilka commented Nov 15, 2021

@sehz how could proc macro approach use Open API spec? The only sensible option I see is to list derived specs and test their conformance to the spec (but that could require some extra Spec details to be available at least when tests are enabled)

@sehz
Copy link
Collaborator Author

sehz commented Nov 22, 2021

procedure macro just generates boiler plate once trait is defined. You can take look at how to parse as in here: https://github.com/infinyon/fluvio/tree/master/crates/fluvio-protocol-derive

@qrilka
Copy link
Contributor

qrilka commented Nov 22, 2021

Sure but I guess the mapping from OpenAPI spec to Spec in Rust code base is a manual thing, right?

@qrilka
Copy link
Contributor

qrilka commented Dec 8, 2021

@sehz I was looking a bit into other things and read a bit more about proc macros and as for this ticket - what actually should a macro derive here?
When I look into https://github.com/infinyon/k8-api/blob/master/src/k8-types/src/core/service.rs then the only things like that seem to be:

  • impl Spec for ServiceSpec (with a bit unclear logic in make_same to empty cluster_ip)
  • default_store_spec!(ServiceSpec, ServiceStatus, "Service") which is already a macro

Do I miss something here?

@sehz
Copy link
Collaborator Author

sehz commented Dec 8, 2021

default_store_spec implements StoreSpec which not part of scope. This is just about reducing boiling plate:

impl Spec for ServiceSpec {
    type Status = ServiceStatus;
    type Header = DefaultHeader;

    fn metadata() -> &'static Crd {
        &SERVICE_API
    }

    fn make_same(&mut self, other: &Self) {
        if other.cluster_ip.is_empty() {
            self.cluster_ip = "".to_owned();
        }
    }
}

@qrilka
Copy link
Contributor

qrilka commented Dec 8, 2021

This doesn't seem to be a lot boilerplate but OK.
The only remaining question then is about this special handling for cluster_ip - is it needed only for string fields or maybe there's some more complicated logic?

@sehz
Copy link
Collaborator Author

sehz commented Dec 8, 2021

Can implement field attributes like #[serde(default = "path")], so this can be something like:

#[kubeapi(group = "core",version ="1", status = ServiceStatus,makesame="same_service")]
...

@qrilka
Copy link
Contributor

qrilka commented Dec 8, 2021

And what does "same_service" mean your example?
As for field attributes, those work OK as (parameterized) markers but it doesn't look like it could allow generic handling, e.g. in StatefulSetSpec I see a different definition:

    // statefulset doesnt' like to change volume claim template
    fn make_same(&mut self, other: &Self) {
        self.volume_claim_templates = other.volume_claim_templates.clone();
    }

So it looks like make_same isn't something that needs to be derived

@sehz
Copy link
Collaborator Author

sehz commented Dec 8, 2021

impl Spec for ServiceSpec {
    type Status = ServiceStatus;
    type Header = DefaultHeader;

    fn metadata() -> &'static Crd {
        &SERVICE_API
    }

    fn make_same(&mut self, other: &Self) {
       make_same2(self,other)
    }
}

fn make_same2(sv1: &mut ServiceSpec,sv2: &mut ServiceSpec2) { 
    if sv2.cluster_ip.is_empty() {
            sv1.cluster_ip = "".to_owned();
        }
}


@qrilka
Copy link
Contributor

qrilka commented Dec 9, 2021

I see but what about changing the types a bit? I think it would be more elegant to split make_same into a separate trait MakeSame, require it only for apply (the only place it seems to be used) and derive the default no-op implementation if needed or allow it to be declared if custom implementation is needed.

@sehz
Copy link
Collaborator Author

sehz commented Dec 9, 2021

Make sense

@pinkforest
Copy link
Contributor

Hey. could we use https://github.com/kube-rs/kube-rs client?

I am happy to port fluvio/#1131 to it and see how well it works?

I have love / hate relationship with the current crop of OpenAPI rust generators :/

@qrilka
Copy link
Contributor

qrilka commented Jan 28, 2022

@pinkforest I asked about kube-rs previously - #92 (comment)
as for infinyon/fluvio#1131 - I've done work on that but didn't have much free time lately

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants