infinyon / k8-api

Native Rust implementation of Kubernetes api
Apache License 2.0
35 stars 17 forks source link

Generate rust bindings from Kubernetes openapi #4

Open sehz opened 4 years ago

sehz commented 4 years ago

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 commented 3 years ago

@qrilka Interested in working on this?

qrilka commented 3 years ago

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 commented 3 years ago

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

sehz commented 3 years ago

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 commented 3 years ago

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

qrilka commented 3 years ago

@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 commented 2 years ago

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 commented 2 years ago

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

qrilka commented 2 years ago

@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:

Do I miss something here?

sehz commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago
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 commented 2 years ago

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 commented 2 years ago

Make sense

pinkforest commented 2 years ago

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 commented 2 years ago

@pinkforest I asked about kube-rs previously - https://github.com/infinyon/k8-api/issues/92#issuecomment-943541525 as for https://github.com/infinyon/fluvio/issues/1131 - I've done work on that but didn't have much free time lately