use serde::{Deserialize, Serialize};
use crate::metadata::medadata::Metadata;
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "PascalCase")]
pub enum Phase {
Pending,
Scheduling,
SchedulerFailed,
ContainerCreating,
ImagePulling,
ImagePullBackOff,
Running,
Succeeded,
Failed,
Terminated,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub struct Pod {
#[serde(rename = "apiVersion")]
pub api_version: String,
pub kind: String,
pub metadata: Metadata,
pub spec: PodSpec,
pub status: Option<PodStatus>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub struct PodSpec {
#[serde(rename = "nodeName")]
pub nodename: Option<String>,
pub hostname: Option<String>,
#[serde(rename = "hostAliases")]
pub host_aliases: Option<Vec<HostAlias>>,
pub containers: Vec<Container>,
pub volumes: Option<Vec<Volume>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub struct HostAlias {
pub ip: String,
pub hostnames: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub struct Container {
pub name: String,
pub image: String,
#[serde(rename = "imagePullPolicy")]
pub image_pull_policy: Option<String>,
pub command: Option<Vec<String>>,
pub args: Option<Vec<String>>,
#[serde(rename = "workingDir")]
pub working_dir: Option<String>,
pub ports: Option<Vec<Port>>,
pub env: Option<Vec<EnvVar>>,
pub resources: Option<ResourceRequirements>,
#[serde(rename = "volumeMounts")]
pub volume_mounts: Option<Vec<VolumeMount>>,
#[serde(rename = "volumeDevices")]
pub volume_devices: Option<Vec<VolumeDevice>>,
#[serde(rename = "securityContext")]
pub security_context: Option<SecurityContext>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub struct Port {
pub name: Option<String>,
#[serde(rename = "containerPort")]
pub container_port: u16,
pub protocol: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub struct EnvVar {
pub name: String,
pub value: Option<String>,
#[serde(rename = "valueFrom")]
pub value_from: Option<ValueFrom>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub struct ValueFrom {
#[serde(rename = "fieldRef")]
pub field_ref: Option<FieldRef>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub struct FieldRef {
#[serde(rename = "fieldPath")]
pub field_path: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub struct ResourceRequirements {
pub requests: Option<Resource>,
pub limits: Option<Resource>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub struct Resource {
pub memory: Option<String>,
pub cpu: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub struct VolumeMount {
pub name: String,
#[serde(rename = "mountPath")]
pub mount_path: String,
#[serde(rename = "readOnly")]
pub read_only: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub struct VolumeDevice {
pub name: String,
#[serde(rename = "devicePath")]
pub device_path: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub struct SecurityContext {
#[serde(rename = "runAsUser")]
pub run_as_user: Option<u32>,
#[serde(rename = "runAsGroup")]
pub run_as_group: Option<u32>,
#[serde(rename = "readOnlyRootFilesystem")]
pub read_only_root_filesystem: Option<bool>,
#[serde(rename = "allowPrivilegeEscalation")]
pub allow_privilege_escalation: Option<bool>,
pub privileged: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub struct Volume {
pub name: String,
#[serde(rename = "configMap")]
pub config_map: Option<ConfigMapVolume>,
#[serde(rename = "emptyDir")]
pub empty_dir: Option<EmptyDirVolume>,
#[serde(rename = "hostPath")]
pub host_path: Option<HostPathVolume>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub struct ConfigMapVolume {
pub name: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub struct EmptyDirVolume {}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub struct HostPathVolume {
pub path: String,
#[serde(rename = "type")]
pub path_type: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub struct PodStatus {
pub phase: Option<Phase>,
pub message: Option<String>,
#[serde(rename = "podIP")]
pub pod_ip: Option<String>,
#[serde(rename = "podIPs")]
pub pod_ips: Option<Vec<PodIPs>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub struct PodIPs {
pub ip: Option<String>,
}
impl PodStatus {
fn new() -> Self {
PodStatus {
phase: Some(Phase::Pending),
message: None,
pod_ip: None,
pod_ips: None,
}
}
}
impl Pod {
pub fn fill_pod_defaults(&mut self) {
self.metadata.fill_metadata_defaults();
self.status = Some(PodStatus::new())
}
}
#[cfg(test)]
mod tests {
use super::*; use chrono::{DateTime, Local};
#[test]
fn test_pod_deserialization() {
let pod_yaml = r#"
apiVersion: v1
kind: Pod
metadata:
name: example-pod
namespace: default
labels:
app: my-app
creationTimestamp: 2024-12-10T08:00:00+08:00
spec:
nodeName: my-node
hostname: my-hostname
hostAliases:
- ip: "127.0.0.1"
hostnames:
- "my-local-host"
- "another-host"
containers:
- name: example-container
image: example-image:latest
imagePullPolicy: IfNotPresent
command: ["nginx"]
args: ["-g", "daemon off;"]
workingDir: /usr/share/nginx/html
ports:
- name: http
containerPort: 80
protocol: TCP
- name: https
containerPort: 443
protocol: TCP
env:
- name: ENV_MODE
value: production
- name: ENV_VERSION
valueFrom:
fieldRef:
fieldPath: metadata.name
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "1"
volumeMounts:
- name: config-volume
mountPath: /etc/nginx/conf.d
readOnly: true
- name: data-volume
mountPath: /usr/share/nginx/html
- name: data-host-volume
mountPath: /usr/share/nginx/a.txt
volumeDevices:
- name: device-volume
devicePath: /dev/sdb
securityContext:
runAsUser: 1000
runAsGroup: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: true
privileged: true
volumes:
- name: example-volume
configMap:
name: nginx-config
- name: data-volume
emptyDir: {}
- name: device-volume
hostPath:
path: /dev/sdb
type: Directory
- name: device-volume
hostPath:
path: /dev/sdb
type: Directory
status:
phase: Pending
message: begin handle
podIP: 10.42.0.9
podIPs:
- ip: 10.42.0.9
"#;
let pod: Pod = serde_yaml::from_str(pod_yaml).expect("Failed to parse YAML");
assert_eq!(pod.api_version, "v1");
assert_eq!(pod.kind, "Pod");
assert_eq!(pod.metadata.name, "example-pod");
assert_eq!(pod.metadata.namespace, "default");
assert_eq!(pod.spec.containers.len(), 1);
assert_eq!(pod.spec.containers[0].name, "example-container");
assert_eq!(pod.spec.containers[0].image, "example-image:latest");
assert_eq!(
pod.spec.volumes.as_ref().unwrap()[0].name,
"example-volume"
);
let creation_time = pod.metadata.creation_timestamp;
assert_eq!(
creation_time.unwrap(),
"2024-12-10T08:00:00+08:00"
.parse::<DateTime<Local>>()
.unwrap()
);
println!("{:#?}", pod);
}
#[test]
fn test_fill_pod_defaults(){
let pod_yaml = r#"
apiVersion: v1
kind: Pod
metadata:
name: example-pod
namespace: default
labels:
app: my-app
spec:
nodeName: my-node
hostname: my-hostname
hostAliases:
- ip: "127.0.0.1"
hostnames:
- "my-local-host"
- "another-host"
containers:
- name: example-container
image: example-image:latest
imagePullPolicy: IfNotPresent
command: ["nginx"]
args: ["-g", "daemon off;"]
workingDir: /usr/share/nginx/html
ports:
- name: http
containerPort: 80
protocol: TCP
- name: https
containerPort: 443
protocol: TCP
env:
- name: ENV_MODE
value: production
- name: ENV_VERSION
valueFrom:
fieldRef:
fieldPath: metadata.name
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "1"
volumeMounts:
- name: config-volume
mountPath: /etc/nginx/conf.d
readOnly: true
- name: data-volume
mountPath: /usr/share/nginx/html
- name: data-host-volume
mountPath: /usr/share/nginx/a.txt
volumeDevices:
- name: device-volume
devicePath: /dev/sdb
securityContext:
runAsUser: 1000
runAsGroup: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: true
privileged: true
volumes:
- name: example-volume
configMap:
name: nginx-config
- name: data-volume
emptyDir: {}
- name: device-volume
hostPath:
path: /dev/sdb
type: Directory
- name: device-volume
hostPath:
path: /dev/sdb
type: Directory
"#;
let mut pod: Pod = serde_yaml::from_str(pod_yaml).expect("Failed to parse YAML");
pod.fill_pod_defaults();
assert_eq!(pod.api_version, "v1");
assert_eq!(pod.kind, "Pod");
assert_eq!(pod.metadata.name, "example-pod");
assert_eq!(pod.metadata.namespace, "default");
assert_eq!(pod.spec.containers.len(), 1);
assert_eq!(pod.spec.containers[0].name, "example-container");
assert_eq!(pod.spec.containers[0].image, "example-image:latest");
assert_eq!(
pod.spec.volumes.as_ref().unwrap()[0].name,
"example-volume"
);
assert_eq!(
format!("{:#?}",pod.clone().status.unwrap().phase.unwrap()),
"Pending"
);
println!("{:#?}", pod.clone());
}
}