Skip to content

Commit

Permalink
clean up api::discovery module - for kube-rs#523 (kube-rs#538)
Browse files Browse the repository at this point in the history
* clean up api::discovery module - for kube-rs#523

- moves the modules from kube::client to kube::api
- introduces easier way to get recommended_resources and
  recommended_kind from an `ApiGroup`
- restructures file for readability (internal Version sorting quirks
  documented and tested)
- renamed `ApiResourceExtras` to `ApiCapabilities`
- renamed `ApiGroup::group` to `ApiGroup::get`
- added support for `Discovery::single` which avoids having to deal with
  the `HashMap` and returns a straight `ApiGroup` (making the
recommended doc examples more compelling)
- documents the version sorting a bit better
- renamed `ApiGroup::preferred_version_or_guess` to
  `ApiGroup::preferred_version_or_latest` and referred to the version
policy
- separated querying to helpers on `ApiGroup`
- moved sorting to `ApiGroup` construction part

* clippy

* changelog

* promote discovery to its own module

* factor out version logic into own private module

* add some docs + small renames

`resources_by_version` -> `versioned_resources`
to match `recommended_resources`.

few more doc links

* remove stray todo and add back kube::client::Status

* add better entry point for the cheaper single case

* fix tests

* restructure completely again for cache/oneshot distinction

actually many legit use cases here, so made it kind of nice.
finally, this allows us to deprecate old ApiResource from_apiresource
as well as old Client listers (which we use internally).

possibly some more cleanups incoming.

* clippy

* split discovery into 3 files

* discovery error type

* fix tests

* rename openapi mod to parse mod

* revert premature deprecation

* document changes

* make DiscoveryError work on config only feat + fix rustfmt makefile

* resolve last questions
  • Loading branch information
clux committed May 31, 2021
1 parent a9a7624 commit a2c7aef
Show file tree
Hide file tree
Showing 22 changed files with 1,005 additions and 552 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Expand Up @@ -2,6 +2,12 @@
UNRELEASED
===================
* see https://github.com/clux/kube-rs/compare/0.55.0...master
* `kube`: added `Api::default_namespaced` - #209 via #534
* `kube`: added `config` feature - #533 via #535
* `kube`: BREAKING: moved `client::discovery` module to `kube::discovery` and rewritten module #523
- `discovery`: added `oneshot` helpers for quick selection of recommended resources / kinds #523
- `discovery`: moved `ApiResource` and `ApiCapabilities` (result of discovery) to `kube_core::discovery`
- BREAKING: removed internal `ApiResource::from_apiresource`

0.55.0 / 2021-05-21
===================
Expand All @@ -10,7 +16,7 @@ UNRELEASED
* `kube`: `api` `discovery` module now uses a new `ApiResource` struct [#495](https://github.com/clux/kube-rs/issues/495) + [#482](https://github.com/clux/kube-rs/issues/482)
* `kube`: `api` BREAKING: `DynamicObject` + `Object` now takes an `ApiResource` rather than a `GroupVersionKind`
* `kube`: `api` BREAKING: `discovery` module's `Group` renamed to `ApiGroup`
* `kube`: `client` BREAKING: `kube::client::Status` moved to `kube::core::Status`
* `kube`: `client` BREAKING: `kube::client::Status` moved to `kube::core::Status` (accidental, re-adding in 0.56)
* `kube-core` crate factored out of `kube` to reduce dependencies - [#516](https://github.com/clux/kube-rs/issues/516) via [#517](https://github.com/clux/kube-rs/issues/517) + [#519](https://github.com/clux/kube-rs/issues/519) + [#522](https://github.com/clux/kube-rs/issues/522) + [#528](https://github.com/clux/kube-rs/issues/528) + [#530](https://github.com/clux/kube-rs/issues/530)
* `kube`: `kube::Service` removed to allow `kube::Client` to take an abritrary `Service<http::Request<hyper::Body>>` - [#532](https://github.com/clux/kube-rs/issues/532)

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Expand Up @@ -9,7 +9,7 @@ clippy:

fmt:
#rustup component add rustfmt --toolchain nightly
cargo +nightly fmt
rustfmt +nightly --edition 2018 **/*/*.rs

doc:
RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --lib --workspace --features=derive,ws,oauth,jsonpatch --open
Expand Down
28 changes: 10 additions & 18 deletions examples/dynamic_api.rs
Expand Up @@ -3,10 +3,8 @@

use kube::{
api::{Api, DynamicObject, ResourceExt},
client::{
discovery::{verbs, Discovery, Scope},
Client,
},
discovery::{verbs, Discovery, Scope},
Client,
};
use log::info;

Expand All @@ -15,31 +13,25 @@ async fn main() -> anyhow::Result<()> {
std::env::set_var("RUST_LOG", "info,kube=debug");
env_logger::init();
let client = Client::try_default().await?;

let v = client.apiserver_version().await?;
info!("api version: {:?}", v);

let ns_filter = std::env::var("NAMESPACE").ok();

let discovery = Discovery::new(&client).await?;

let discovery = Discovery::new(client.clone()).run().await?;
for group in discovery.groups() {
let ver = group.preferred_version_or_guess();
for (api_res, extras) in group.resources_by_version(ver) {
if !extras.supports_operation(verbs::LIST) {
for (ar, caps) in group.recommended_resources() {
if !caps.supports_operation(verbs::LIST) {
continue;
}
let api: Api<DynamicObject> = if extras.scope == Scope::Namespaced {
let api: Api<DynamicObject> = if caps.scope == Scope::Namespaced {
if let Some(ns) = &ns_filter {
Api::namespaced_with(client.clone(), ns, &api_res)
Api::namespaced_with(client.clone(), ns, &ar)
} else {
Api::all_with(client.clone(), &api_res)
Api::all_with(client.clone(), &ar)
}
} else {
Api::all_with(client.clone(), &api_res)
Api::all_with(client.clone(), &ar)
};

info!("{}/{} : {}", group.name(), ver, api_res.kind);
info!("{}/{} : {}", group.name(), ar.version, ar.kind);

let list = api.list(&Default::default()).await?;
for item in list.items {
Expand Down
20 changes: 6 additions & 14 deletions examples/dynamic_watcher.rs
@@ -1,7 +1,7 @@
use futures::prelude::*;
use kube::{
api::{ApiResource, DynamicObject, GroupVersionKind, ListParams, ResourceExt},
Api, Client,
api::{DynamicObject, GroupVersionKind, ListParams, ResourceExt},
discovery, Api, Client,
};
use kube_runtime::{utils::try_flatten_applied, watcher};
use std::env;
Expand All @@ -19,19 +19,11 @@ async fn main() -> anyhow::Result<()> {

// Turn them into a GVK
let gvk = GroupVersionKind::gvk(&group, &version, &kind);
let mut api_resource = ApiResource::from_gvk(&gvk);
// Use API discovery to identify more information about the type (like its plural)
let (ar, _caps) = discovery::pinned_kind(&client, &gvk).await?;

if let Some(resource) = env::var("RESOURCE").ok() {
api_resource.plural = resource;
} else {
println!(
"Using inferred plural name (use RESOURCE to override): {}",
api_resource.plural
);
}

// Use them in an Api with the GVK as its DynamicType
let api = Api::<DynamicObject>::all_with(client, &api_resource);
// Use the discovered kind in an Api with the ApiResource as its DynamicType
let api = Api::<DynamicObject>::all_with(client, &ar);

// Fully compatible with kube-runtime
let watcher = watcher(api, ListParams::default());
Expand Down
102 changes: 0 additions & 102 deletions kube-core/src/api_resource.rs

This file was deleted.

109 changes: 109 additions & 0 deletions kube-core/src/discovery.rs
@@ -0,0 +1,109 @@
//! Type information structs for API discovery
use crate::{gvk::GroupVersionKind, resource::Resource};

/// Information about a Kubernetes API resource
///
/// Enough information to use it like a `Resource` by passing it to the dynamic `Api`
/// constructors like `Api::all_with` and `Api::namespaced_with`.
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct ApiResource {
/// Resource group, empty for core group.
pub group: String,
/// group version
pub version: String,
/// apiVersion of the resource (v1 for core group,
/// groupName/groupVersions for other).
pub api_version: String,
/// Singular PascalCase name of the resource
pub kind: String,
/// Plural name of the resource
pub plural: String,
}

impl ApiResource {
/// Creates an ApiResource by type-erasing a Resource
pub fn erase<K: Resource>(dt: &K::DynamicType) -> Self {
ApiResource {
group: K::group(dt).to_string(),
version: K::version(dt).to_string(),
api_version: K::api_version(dt).to_string(),
kind: K::kind(dt).to_string(),
plural: K::plural(dt).to_string(),
}
}

/// Creates an ApiResource from group, version, kind and plural name.
pub fn from_gvk_with_plural(gvk: &GroupVersionKind, plural: &str) -> Self {
ApiResource {
api_version: gvk.api_version(),
group: gvk.group.clone(),
version: gvk.version.clone(),
kind: gvk.kind.clone(),
plural: plural.to_string(),
}
}

/// Creates an ApiResource from group, version and kind.
///
/// # Warning
/// This function will **guess** the resource plural name.
/// Usually, this is ok, but for CRDs with complex pluralisations it can fail.
/// If you are getting your values from `kube_derive` use the generated method for giving you an [`ApiResource`].
/// Otherwise consider using [`ApiResource::from_gvk_with_plural`](crate::discovery::ApiResource::from_gvk_with_plural)
/// to explicitly set the plural, or run api discovery on it via `kube::discovery`.
pub fn from_gvk(gvk: &GroupVersionKind) -> Self {
ApiResource::from_gvk_with_plural(gvk, &crate::resource::to_plural(&gvk.kind.to_ascii_lowercase()))
}
}


/// Resource scope
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub enum Scope {
/// Objects are global
Cluster,
/// Each object lives in namespace.
Namespaced,
}

/// Rbac verbs for ApiCapabilities
pub mod verbs {
/// Create a resource
pub const CREATE: &str = "create";
/// Get single resource
pub const GET: &str = "get";
/// List objects
pub const LIST: &str = "list";
/// Watch for objects changes
pub const WATCH: &str = "watch";
/// Delete single object
pub const DELETE: &str = "delete";
/// Delete multiple objects at once
pub const DELETE_COLLECTION: &str = "deletecollection";
/// Update an object
pub const UPDATE: &str = "update";
/// Patch an object
pub const PATCH: &str = "patch";
}

/// Contains the capabilities of an API resource
#[derive(Debug, Clone)]
pub struct ApiCapabilities {
/// Scope of the resource
pub scope: Scope,
/// Available subresources.
///
/// Please note that returned ApiResources are not standalone resources.
/// Their name will be of form `subresource_name`, not `resource_name/subresource_name`.
/// To work with subresources, use `Request` methods for now.
pub subresources: Vec<(ApiResource, ApiCapabilities)>,
/// Supported operations on this resource
pub operations: Vec<String>,
}

impl ApiCapabilities {
/// Checks that given verb is supported on this resource.
pub fn supports_operation(&self, operation: &str) -> bool {
self.operations.iter().any(|op| op == operation)
}
}
5 changes: 2 additions & 3 deletions kube-core/src/dynamic.rs
Expand Up @@ -2,7 +2,7 @@
//!
//! For concrete usage see [examples prefixed with dynamic_](https://github.com/clux/kube-rs/tree/master/examples).

pub use crate::api_resource::ApiResource;
pub use crate::discovery::ApiResource;
use crate::{metadata::TypeMeta, resource::Resource};
use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
use std::borrow::Cow;
Expand Down Expand Up @@ -87,8 +87,7 @@ impl Resource for DynamicObject {
#[cfg(test)]
mod test {
use crate::{
api_resource::ApiResource,
dynamic::DynamicObject,
dynamic::{ApiResource, DynamicObject},
gvk::GroupVersionKind,
params::{Patch, PatchParams, PostParams},
request::Request,
Expand Down
4 changes: 4 additions & 0 deletions kube-core/src/error.rs
Expand Up @@ -15,6 +15,10 @@ pub enum Error {
/// Http based error
#[error("HttpError: {0}")]
HttpError(#[from] http::Error),

/// Invalid GroupVersion
#[error("Invalid GroupVersion: {0}")]
InvalidGroupVersion(String),
}

/// An error response from the API.
Expand Down

0 comments on commit a2c7aef

Please sign in to comment.