Este documento ha sido traducido utilizando tecnología de traducción automática. Si bien nos esforzamos por proporcionar traducciones precisas, no ofrecemos garantías sobre la integridad, precisión o confiabilidad del contenido traducido. En caso de discrepancia, la versión original en inglés prevalecerá y constituirá el texto autorizado.

Esta es documentación inédita para Admission Controller 1.34-dev.

Escribiendo lógica de validación

Es hora de escribir el código de validación real. Está definido en el archivo src/lib.rs. En este archivo puedes encontrar una función llamada validate.

Esta es la función proporcionada como andamiaje:

fn validate(payload: &[u8]) -> CallResult {
    let validation_request: ValidationRequest<Settings> = ValidationRequest::new(payload)?; (1)

    info!(LOG_DRAIN, "starting validation");
    if validation_request.request.kind.kind != apicore::Pod::KIND {
        warn!(LOG_DRAIN, "Policy validates Pods only. Accepting resource"; "kind" => &validation_request.request.kind.kind);
        return kubewarden::accept_request();
    }
    // TODO: you can unmarshal any Kubernetes API type you are interested in (2)
    match serde_json::from_value::<apicore::Pod>(validation_request.request.object) {
        Ok(pod) => {
            // TODO: your logic goes here (3)
            if pod.metadata.name == Some("invalid-pod-name".to_string()) {
                let pod_name = pod.metadata.name.unwrap();
                info!(
                    LOG_DRAIN,
                    "rejecting pod";
                    "pod_name" => &pod_name
                );
                kubewarden::reject_request(
                    Some(format!("pod name {} is not accepted", &pod_name)),
                    None,
                    None,
                    None,
                )
            } else {
                info!(LOG_DRAIN, "accepting resource");
                kubewarden::accept_request()
            }
        }
        Err(_) => {
            // TODO: handle as you wish
            // We were forwarded a request we cannot unmarshal or
            // understand, just accept it
            warn!(LOG_DRAIN, "cannot unmarshal resource: this policy does not know how to evaluate this resource; accept it");
            kubewarden::accept_request() (4)
        }
    }
}

Revisando el listado de código:

  • En la línea marcada ➀. Analiza el payload entrante en un objeto ValidationRequest<Setting>. Esto llena automáticamente la instancia Settings dentro del ValidationRequest con los parámetros proporcionados por el usuario.

  • En la línea marcada ➁. Convierte el objeto JSON en bruto de Kubernetes incrustado en la solicitud en una instancia del struct Pod

  • En la línea marcada ➂. La solicitud tiene un objeto Pod, el código solo aprueba las solicitudes que no tienen metadata.name igual al valor codificado invalid-pod-name.

  • En la línea marcada ➃. La solicitud no contiene un objeto Pod, por lo tanto, la directiva acepta la solicitud.

Como puedes ver, el código ya está realizando una validación que se asemeja a la que deseas implementar. Solo tienes que eliminar el valor codificado y usar los valores proporcionados por el usuario a través de la configuración de la directiva.

Puedes hacerlo reemplazando la función de andamiaje validate en src/lib.rs con esta:

fn validate(payload: &[u8]) -> CallResult {
    let validation_request: ValidationRequest<Settings> = ValidationRequest::new(payload)?;

    info!(LOG_DRAIN, "starting validation");
    if validation_request.request.kind.kind != apicore::Pod::KIND {
        warn!(LOG_DRAIN, "Policy validates Pods only. Accepting resource"; "kind" => &validation_request.request.kind.kind);
        return kubewarden::accept_request();
    }

    match serde_json::from_value::<apicore::Pod>(validation_request.request.object) {
        Ok(pod) => {
            let pod_name = pod.metadata.name.unwrap_or_default();
            if validation_request
                .settings
                .invalid_names
                .contains(&pod_name)
            {
                kubewarden::reject_request(
                    Some(format!("pod name {:?} is not accepted", pod_name)),
                    None,
                    None,
                    None,
                )
            } else {
                kubewarden::accept_request()
            }
        }
        Err(_) => {
            // We were forwarded a request we cannot unmarshal or
            // understand, just accept it
            kubewarden::accept_request()
        }
    }
}

Pruebas unitarias

Finalmente, puedes crear pruebas unitarias para comprobar que el código de validación funciona como se espera.

El archivo lib.rs ya tiene pruebas definidas al final del archivo, y como puedes ver, el SDK de Rust de Admission Controller también proporciona ayudantes para pruebas.

Además, el proyecto de andamiaje ya incluye fixtures de prueba por defecto en el directorio test_data. Vas a utilizar estas solicitudes de admisión grabadas para escribir tus pruebas unitarias.

Cambia el contenido de la sección de pruebas al final de src/lib.rs para que se vea así:

#[cfg(test)]
mod tests {
    use super::*;

    use kubewarden_policy_sdk::test::Testcase;
    use std::collections::HashSet;

    #[test]
    fn accept_pod_with_valid_name() -> Result<(), ()> {
        let mut invalid_names = HashSet::new();
        invalid_names.insert(String::from("bad_name1"));
        let settings = Settings { invalid_names };

        let request_file = "test_data/pod_creation.json";
        let tc = Testcase {
            name: String::from("Pod creation with valid name"),
            fixture_file: String::from(request_file),
            expected_validation_result: true,
            settings,
        };

        let res = tc.eval(validate).unwrap();
        assert!(
            res.mutated_object.is_none(),
            "Something mutated with test case: {}",
            tc.name,
        );

        Ok(())
    }

    #[test]
    fn reject_pod_with_invalid_name() -> Result<(), ()> {
        let mut invalid_names = HashSet::new();
        invalid_names.insert(String::from("nginx"));
        let settings = Settings { invalid_names };

        let request_file = "test_data/pod_creation.json";
        let tc = Testcase {
            name: String::from("Pod creation with invalid name"),
            fixture_file: String::from(request_file),
            expected_validation_result: false,
            settings,
        };

        let res = tc.eval(validate).unwrap();
        assert!(
            res.mutated_object.is_none(),
            "Something mutated with test case: {}",
            tc.name,
        );

        Ok(())
    }

    #[test]
    fn accept_request_with_non_pod_resource() -> Result<(), ()> {
        let mut invalid_names = HashSet::new();
        invalid_names.insert(String::from("prod"));
        let settings = Settings { invalid_names };

        let request_file = "test_data/ingress_creation.json";
        let tc = Testcase {
            name: String::from("Ingress creation"),
            fixture_file: String::from(request_file),
            expected_validation_result: true,
            settings,
        };

        let res = tc.eval(validate).unwrap();
        assert!(
            res.mutated_object.is_none(),
            "Something mutated with test case: {}",
            tc.name,
        );

        Ok(())
    }
}

Ahora tienes tres pruebas unitarias definidas en lib.rs:

  • accept_pod_with_valid_name: acepta un Pod con un nombre válido

  • reject_pod_with_invalid_name: rechaza un Pod con un nombre inválido

  • accept_request_with_non_pod_resource: acepta solicitudes que no tienen un Pod como objeto

Puedes ejecutar las pruebas unitarias de nuevo:

$ cargo test
   Compiling demo v0.1.0 (/home/flavio/hacking/kubernetes/kubewarden/demo)
    Finished test [unoptimized + debuginfo] target(s) in 3.45s
     Running target/debug/deps/demo-24670dd6a538fd72

running 5 tests
test settings::tests::accept_settings_with_a_list_of_invalid_names ... ok
test settings::tests::reject_settings_without_a_list_of_invalid_names ... ok
test tests::accept_request_with_non_pod_resource ... ok
test tests::accept_pod_with_valid_name ... ok
test tests::reject_pod_with_invalid_name ... ok

test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Eso es todo lo que se requiere si necesitas escribir una directiva de validación simple.