1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
//! Metadata structs used in traits, lists, and dynamic objects.
use std::{borrow::Cow, marker::PhantomData};
pub use k8s_openapi::apimachinery::pkg::apis::meta::v1::{ListMeta, ObjectMeta};
use serde::{Deserialize, Serialize};
use crate::{DynamicObject, Resource};
/// Type information that is flattened into every kubernetes object
#[derive(Deserialize, Serialize, Clone, Default, Debug, Eq, PartialEq, Hash)]
#[serde(rename_all = "camelCase")]
pub struct TypeMeta {
/// The version of the API
pub api_version: String,
/// The name of the API
pub kind: String,
}
/// A generic representation of any object with `ObjectMeta`.
///
/// It allows clients to get access to a particular `ObjectMeta`
/// schema without knowing the details of the version.
///
/// See the [`PartialObjectMetaExt`] trait for how to construct one safely.
#[derive(Deserialize, Serialize, Clone, Default, Debug)]
#[serde(rename_all = "camelCase")]
pub struct PartialObjectMeta<K = DynamicObject> {
/// The type fields, not always present
#[serde(flatten, default)]
pub types: Option<TypeMeta>,
/// Standard object's metadata
#[serde(default)]
pub metadata: ObjectMeta,
/// Type information for static dispatch
#[serde(skip, default)]
pub _phantom: PhantomData<K>,
}
mod private {
pub trait Sealed {}
impl Sealed for super::ObjectMeta {}
}
/// Helper trait for converting `ObjectMeta` into useful `PartialObjectMeta` variants
pub trait PartialObjectMetaExt: private::Sealed {
/// Convert `ObjectMeta` into a Patch-serializable `PartialObjectMeta`
///
/// This object can be passed to `Patch::Apply` and used with `Api::patch_metadata`,
/// for an `Api<K>` using the underlying types `TypeMeta`:
///
/// ```
/// # use k8s_openapi::api::core::v1::Pod;
/// # use kube::core::{ObjectMeta, PartialObjectMetaExt, ResourceExt};
/// let partial = ObjectMeta {
/// labels: Some([("key".to_string(), "value".to_string())].into()),
/// ..Default::default()
/// }.into_request_partial::<Pod>();
///
/// // request partials are generally closer to patches than fully valid resources:
/// assert_eq!(partial.name_any(), "");
///
/// // typemeta is re-used from K:
/// assert_eq!(partial.types.unwrap().kind, "Pod");
/// ```
fn into_request_partial<K: Resource<DynamicType = ()>>(self) -> PartialObjectMeta<K>;
/// Convert `ObjectMeta` into a response object for a specific `Resource`
///
/// This object emulates a response object and **cannot** be used in request bodies
/// because it contains erased `TypeMeta` (and the apiserver is doing the erasing).
///
/// This method is **mostly useful for unit testing** behaviour.
///
/// ```
/// # use k8s_openapi::api::apps::v1::Deployment;
/// # use kube::core::{ObjectMeta, PartialObjectMetaExt, ResourceExt};
/// let partial = ObjectMeta {
/// name: Some("my-deploy".to_string()),
/// namespace: Some("default".to_string()),
/// ..Default::default()
/// }.into_response_partial::<Deployment>();
///
/// assert_eq!(partial.name_any(), "my-deploy");
/// assert_eq!(partial.types.unwrap().kind, "PartialObjectMetadata"); // NB: Pod erased
/// ```
fn into_response_partial<K>(self) -> PartialObjectMeta<K>;
}
impl PartialObjectMetaExt for ObjectMeta {
fn into_request_partial<K: Resource<DynamicType = ()>>(self) -> PartialObjectMeta<K> {
PartialObjectMeta {
types: Some(TypeMeta {
api_version: K::api_version(&()).into(),
kind: K::kind(&()).into(),
}),
metadata: self,
_phantom: PhantomData,
}
}
fn into_response_partial<K>(self) -> PartialObjectMeta<K> {
PartialObjectMeta {
types: Some(TypeMeta {
api_version: "meta.k8s.io/v1".to_string(),
kind: "PartialObjectMetadata".to_string(),
}),
metadata: self,
_phantom: PhantomData,
}
}
}
impl<K: Resource> Resource for PartialObjectMeta<K> {
type DynamicType = K::DynamicType;
type Scope = K::Scope;
fn kind(dt: &Self::DynamicType) -> Cow<'_, str> {
K::kind(dt)
}
fn group(dt: &Self::DynamicType) -> Cow<'_, str> {
K::group(dt)
}
fn version(dt: &Self::DynamicType) -> Cow<'_, str> {
K::version(dt)
}
fn plural(dt: &Self::DynamicType) -> Cow<'_, str> {
K::plural(dt)
}
fn meta(&self) -> &ObjectMeta {
&self.metadata
}
fn meta_mut(&mut self) -> &mut ObjectMeta {
&mut self.metadata
}
}
#[cfg(test)]
mod test {
use super::{ObjectMeta, PartialObjectMeta, PartialObjectMetaExt};
use crate::Resource;
use k8s_openapi::api::core::v1::Pod;
#[test]
fn can_convert_and_derive_partial_metadata() {
// can use generic type for static dispatch;
assert_eq!(PartialObjectMeta::<Pod>::kind(&()), "Pod");
assert_eq!(PartialObjectMeta::<Pod>::api_version(&()), "v1");
// can convert from objectmeta to partials for different use cases:
let meta = ObjectMeta {
name: Some("mypod".into()),
..Default::default()
};
let request_pom = meta.clone().into_request_partial::<Pod>();
let response_pom = meta.into_response_partial::<Pod>();
// they both basically just inline the metadata;
assert_eq!(request_pom.metadata.name, Some("mypod".to_string()));
assert_eq!(response_pom.metadata.name, Some("mypod".to_string()));
// the request_pom will use the TypeMeta from K to support POST/PUT requests
assert_eq!(request_pom.types.as_ref().unwrap().api_version, "v1");
assert_eq!(request_pom.types.as_ref().unwrap().kind, "Pod");
// but the response_pom will use the type-erased kinds from the apiserver
assert_eq!(response_pom.types.as_ref().unwrap().api_version, "meta.k8s.io/v1");
assert_eq!(response_pom.types.as_ref().unwrap().kind, "PartialObjectMetadata");
}
}