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 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
//! 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,
}
impl TypeMeta {
/// Construct a new `TypeMeta` for the object list from the given resource.
///
/// ```
/// # use k8s_openapi::api::core::v1::Pod;
/// # use kube_core::TypeMeta;
///
/// let type_meta = TypeMeta::list::<Pod>();
/// assert_eq!(type_meta.kind, "PodList");
/// assert_eq!(type_meta.api_version, "v1");
/// ```
pub fn list<K: Resource<DynamicType = ()>>() -> Self {
TypeMeta {
api_version: K::api_version(&()).into(),
kind: K::kind(&()).to_string() + "List",
}
}
/// Construct a new `TypeMeta` for the object from the given resource.
///
/// ```
/// # use k8s_openapi::api::core::v1::Pod;
/// # use kube_core::TypeMeta;
///
/// let type_meta = TypeMeta::resource::<Pod>();
/// assert_eq!(type_meta.kind, "Pod");
/// assert_eq!(type_meta.api_version, "v1");
/// ```
pub fn resource<K: Resource<DynamicType = ()>>() -> Self {
TypeMeta {
api_version: K::api_version(&()).into(),
kind: K::kind(&()).into(),
}
}
}
/// 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");
}
}