use k8s_openapi::api::core::v1::Pod;
use k8s_openapi::apimachinery::pkg::apis::meta::v1::{ObjectMeta, OwnerReference};
use kube::Error as KubeError;
use kube::api::Api;
use tracing::info;
use crate::error::AgentError;
use crate::provider::validate_owner_pod;
use super::super::cleanup::{delete_and_wait, owner_selection};
use super::super::common::create_client;
use super::K8sEphemeralProvider;
#[derive(Debug, Clone)]
pub(in super::super) struct OwnerPod {
name: String,
uid: String,
}
fn failure(stderr: String) -> AgentError {
AgentError::ProcessFailed {
exit_code: -1,
stderr,
}
}
impl OwnerPod {
pub(in super::super) fn validate(&self) -> Result<(), AgentError> {
validate_owner_pod(&self.name, &self.uid).map_err(failure)
}
pub(in super::super) fn reference(&self) -> OwnerReference {
OwnerReference {
api_version: "v1".to_string(),
kind: "Pod".to_string(),
name: self.name.clone(),
uid: self.uid.clone(),
controller: Some(false),
block_owner_deletion: Some(false),
}
}
pub(in super::super) async fn check_present(
&self,
pods: &Api<Pod>,
namespace: &str,
) -> Result<(), AgentError> {
let name = &self.name;
let pod = match pods.get(name).await {
Ok(pod) => pod,
Err(KubeError::Api(e)) if e.code == 404 => {
return Err(failure(format!(
"owner pod '{name}' not found in namespace '{namespace}': an ownerReference only \
holds in the namespace of its owner, so the worker must run in the namespace \
of the pods it creates"
)));
}
Err(e) => return Err(failure(format!("failed to read owner pod '{name}': {e}"))),
};
match pod.metadata.uid.as_deref() {
Some(uid) if uid == self.uid => Ok(()),
actual => Err(failure(format!(
"owner pod '{name}' has uid '{}', not '{}': pass the uid of the running worker pod \
(downward API metadata.uid)",
actual.unwrap_or("<none>"),
self.uid
))),
}
}
}
pub(in super::super) fn has_owner(meta: &ObjectMeta, owned: impl Fn(&str) -> bool) -> bool {
meta.owner_references
.iter()
.flatten()
.any(|reference| owned(&reference.uid))
}
impl K8sEphemeralProvider {
pub fn owner_pod(mut self, name: &str, uid: &str) -> Self {
self.owner = Some(OwnerPod {
name: name.to_string(),
uid: uid.to_string(),
});
self
}
pub async fn release_owned(&self) -> Result<(), AgentError> {
let owner = self.owner.as_ref().ok_or_else(|| {
failure("release_owned needs owner_pod: no owner to release".to_string())
})?;
owner.validate()?;
let client = create_client(&self.cluster_config).await?;
let pods: Api<Pod> = Api::namespaced(client.clone(), &self.namespace);
owner.check_present(&pods, &self.namespace).await?;
let selection = owner_selection(&owner.name, &owner.uid);
let limit = self.previous_attempt_timeout;
let deleted = delete_and_wait(&client, &self.namespace, &selection, limit).await?;
if deleted > 0 {
info!(owner = %owner.name, pods_deleted = deleted, "released the pods of a previous worker process");
}
Ok(())
}
}