regent-sdk 0.3.2

Multi-paradigm configuration management system as a library
Documentation
pub mod package;
pub mod shell;
pub mod system;
pub mod utilities;

use serde::{Deserialize, Serialize};

use crate::error::Error;
use crate::hosts::managed_host::InternalApiCallOutcome;
use crate::hosts::privilege::Privilege;
use crate::hosts::properties::HostProperties;
use crate::state::attribute::package::apt::AptApiCall;
use crate::state::attribute::package::apt::AptBlockExpectedState;
use crate::state::attribute::package::yumdnf::YumDnfApiCall;
use crate::state::attribute::package::yumdnf::YumDnfBlockExpectedState;
use crate::state::attribute::shell::command::CommandApiCall;
use crate::state::attribute::shell::command::CommandBlockExpectedState;
use crate::state::attribute::system::service::ServiceApiCall;
use crate::state::attribute::system::service::ServiceBlockExpectedState;
use crate::state::attribute::utilities::debug::DebugApiCall;
use crate::state::attribute::utilities::debug::DebugBlockExpectedState;
use crate::state::attribute::utilities::lineinfile::LineInFileApiCall;
use crate::state::attribute::utilities::lineinfile::LineInFileBlockExpectedState;
use crate::state::attribute::utilities::ping::PingApiCall;
use crate::state::attribute::utilities::ping::PingBlockExpectedState;
use crate::state::compliance::AttributeComplianceAssessment;
use crate::state::compliance::AttributeComplianceResult;
use crate::state::compliance::AttributeComplianceStatus;
use crate::{
    hosts::handlers::HostHandler,
    hosts::managed_host::{AssessCompliance, ReachCompliance},
    state::attribute::package::pacman::{PacmanApiCall, PacmanBlockExpectedState},
};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Attribute {
    pub privilege: Privilege,
    detail: AttributeDetail,
}

impl Attribute {
    pub fn from(detail: AttributeDetail, privilege: Privilege) -> Attribute {
        Attribute { privilege, detail }
    }

    /// Result because the assessment might fail. If it succeeds, it will return either None (AKA already compliant) or Some(Vec<Remediation>) (AKA what shall be done to reach the expected state).
    pub fn assess<Handler: HostHandler>(
        &self,
        host_handler: &mut Handler,
        host_properties: &Option<HostProperties>,
    ) -> Result<AttributeComplianceAssessment, Error> {
        self.detail
            .assess(host_handler, host_properties, &self.privilege)
    }

    pub fn reach_compliance<Handler: HostHandler>(
        &self,
        host_handler: &mut Handler,
        host_properties: &Option<HostProperties>,
    ) -> Result<AttributeComplianceResult, Error> {
        self.detail
            .reach_compliance(host_handler, host_properties, &self.privilege)
    }

    // Convenience methods for attributes building

    pub fn apt(details: AptBlockExpectedState, privilege: Privilege) -> Attribute {
        Attribute::from(AttributeDetail::Apt(details), privilege)
    }

    pub fn pacman(details: PacmanBlockExpectedState, privilege: Privilege) -> Attribute {
        Attribute::from(AttributeDetail::Pacman(details), privilege)
    }

    pub fn yumdnf(details: YumDnfBlockExpectedState, privilege: Privilege) -> Attribute {
        Attribute::from(AttributeDetail::YumDnf(details), privilege)
    }

    pub fn command(details: CommandBlockExpectedState, privilege: Privilege) -> Attribute {
        Attribute::from(AttributeDetail::Command(details), privilege)
    }

    pub fn service(details: ServiceBlockExpectedState, privilege: Privilege) -> Attribute {
        Attribute::from(AttributeDetail::Service(details), privilege)
    }

    pub fn debug(details: DebugBlockExpectedState, privilege: Privilege) -> Attribute {
        Attribute::from(AttributeDetail::Debug(details), privilege)
    }

    pub fn lineinfile(details: LineInFileBlockExpectedState, privilege: Privilege) -> Attribute {
        Attribute::from(AttributeDetail::LineInFile(details), privilege)
    }

    pub fn ping(details: PingBlockExpectedState, privilege: Privilege) -> Attribute {
        Attribute::from(AttributeDetail::Ping(details), privilege)
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum AttributeDetail {
    Apt(AptBlockExpectedState),
    YumDnf(YumDnfBlockExpectedState),
    Pacman(PacmanBlockExpectedState),
    LineInFile(LineInFileBlockExpectedState),
    Debug(DebugBlockExpectedState),
    Ping(PingBlockExpectedState),
    Service(ServiceBlockExpectedState),
    Command(CommandBlockExpectedState),
}

impl AttributeDetail {
    pub fn assess<Handler: HostHandler>(
        &self,
        host_handler: &mut Handler,
        host_properties: &Option<HostProperties>,
        privilege: &Privilege,
    ) -> Result<AttributeComplianceAssessment, Error> {
        match self {
            AttributeDetail::Apt(expected_state_criteria) => {
                expected_state_criteria.assess_compliance(host_handler, host_properties, privilege)
            }
            AttributeDetail::YumDnf(expected_state_criteria) => {
                expected_state_criteria.assess_compliance(host_handler, host_properties, privilege)
            }
            AttributeDetail::Pacman(expected_state_criteria) => {
                expected_state_criteria.assess_compliance(host_handler, host_properties, privilege)
            }
            AttributeDetail::LineInFile(expected_state_criteria) => {
                expected_state_criteria.assess_compliance(host_handler, host_properties, privilege)
            }
            AttributeDetail::Debug(expected_state_criteria) => {
                expected_state_criteria.assess_compliance(host_handler, host_properties, privilege)
            }
            AttributeDetail::Ping(expected_state_criteria) => {
                expected_state_criteria.assess_compliance(host_handler, host_properties, privilege)
            }
            AttributeDetail::Service(expected_state_criteria) => {
                expected_state_criteria.assess_compliance(host_handler, host_properties, privilege)
            }
            AttributeDetail::Command(expected_state_criteria) => {
                expected_state_criteria.assess_compliance(host_handler, host_properties, privilege)
            }
        }
    }

    pub fn reach_compliance<Handler: HostHandler>(
        &self,
        host_handler: &mut Handler,
        host_properties: &Option<HostProperties>,
        privilege: &Privilege,
    ) -> Result<AttributeComplianceResult, Error> {
        match self.assess(host_handler, host_properties, privilege) {
            Ok(attribute_compliance) => match attribute_compliance {
                AttributeComplianceAssessment::Compliant => Ok(AttributeComplianceResult::from(
                    AttributeComplianceStatus::AlreadyCompliant,
                    None,
                )),
                AttributeComplianceAssessment::NonCompliant(remediations) => {
                    if remediations.len() == 0 {
                        return Err(Error::InternalLogicError(format!(
                            "This should not have been called as the ManagedHost is already compliant"
                        )));
                    }

                    let mut actions_taken: Vec<(Remediation, InternalApiCallOutcome)> = Vec::new();

                    for remediation in remediations {
                        let (remediation, internal_api_call_outcome) = match &remediation {
                            Remediation::None(message) => {
                                return Err(Error::InternalLogicError(format!(
                                    "Remediation::None({}) : get rid of this",
                                    message
                                )));
                            }
                            Remediation::Pacman(attribute_api_call) => {
                                match attribute_api_call.call(host_handler, host_properties) {
                                    Ok(internal_api_call_outcome) => {
                                        (remediation, internal_api_call_outcome)
                                    }
                                    Err(error_detail) => {
                                        return Err(error_detail);
                                    }
                                }
                            }
                            Remediation::Apt(attribute_api_call) => {
                                match attribute_api_call.call(host_handler, host_properties) {
                                    Ok(internal_api_call_outcome) => {
                                        (remediation, internal_api_call_outcome)
                                    }
                                    Err(error_detail) => {
                                        return Err(error_detail);
                                    }
                                }
                            }
                            Remediation::YumDnf(attribute_api_call) => {
                                match attribute_api_call.call(host_handler, host_properties) {
                                    Ok(internal_api_call_outcome) => {
                                        (remediation, internal_api_call_outcome)
                                    }
                                    Err(error_detail) => {
                                        return Err(error_detail);
                                    }
                                }
                            }
                            Remediation::LineInFile(attribute_api_call) => {
                                match attribute_api_call.call(host_handler, host_properties) {
                                    Ok(internal_api_call_outcome) => {
                                        (remediation, internal_api_call_outcome)
                                    }
                                    Err(error_detail) => {
                                        return Err(error_detail);
                                    }
                                }
                            }
                            Remediation::Debug(attribute_api_call) => {
                                match attribute_api_call.call(host_handler, host_properties) {
                                    Ok(internal_api_call_outcome) => {
                                        (remediation, internal_api_call_outcome)
                                    }
                                    Err(error_detail) => {
                                        return Err(error_detail);
                                    }
                                }
                            }
                            Remediation::Ping(attribute_api_call) => {
                                match attribute_api_call.call(host_handler, host_properties) {
                                    Ok(internal_api_call_outcome) => {
                                        (remediation, internal_api_call_outcome)
                                    }
                                    Err(error_detail) => {
                                        return Err(error_detail);
                                    }
                                }
                            }
                            Remediation::Service(attribute_api_call) => {
                                match attribute_api_call.call(host_handler, host_properties) {
                                    Ok(internal_api_call_outcome) => {
                                        (remediation, internal_api_call_outcome)
                                    }
                                    Err(error_detail) => {
                                        return Err(error_detail);
                                    }
                                }
                            }
                            Remediation::Command(attribute_api_call) => {
                                match attribute_api_call.call(host_handler, host_properties) {
                                    Ok(internal_api_call_outcome) => {
                                        (remediation, internal_api_call_outcome)
                                    }
                                    Err(error_detail) => {
                                        return Err(error_detail);
                                    }
                                }
                            }
                        };

                        actions_taken.push((remediation, internal_api_call_outcome.clone()));

                        if let InternalApiCallOutcome::Failure(_detail) = &internal_api_call_outcome
                        {
                            return Ok(AttributeComplianceResult::from(
                                AttributeComplianceStatus::FailedReachedCompliance,
                                Some(actions_taken),
                            ));
                        }
                    }

                    Ok(AttributeComplianceResult::from(
                        AttributeComplianceStatus::ReachedCompliance,
                        Some(actions_taken),
                    ))
                }
            },
            Err(error_detail) => Err(error_detail),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Remediation {
    None(String),
    Pacman(PacmanApiCall),
    Apt(AptApiCall),
    YumDnf(YumDnfApiCall),
    LineInFile(LineInFileApiCall),
    Debug(DebugApiCall),
    Ping(PingApiCall),
    Service(ServiceApiCall),
    Command(CommandApiCall),
}

impl Remediation {
    pub fn reach_compliance<Handler: HostHandler>(
        &self,
        host_handler: &mut Handler,
        host_properties: &Option<HostProperties>,
    ) -> Result<InternalApiCallOutcome, Error> {
        match self {
            Remediation::None(_) => {
                // This case should not occur here according to current logic
                Err(Error::InternalLogicError(String::from(
                    "Unexpected remediation",
                )))
            }
            Remediation::Pacman(api_call) => api_call.call(host_handler, host_properties),
            Remediation::Apt(api_call) => api_call.call(host_handler, host_properties),
            Remediation::YumDnf(api_call) => api_call.call(host_handler, host_properties),
            Remediation::LineInFile(api_call) => api_call.call(host_handler, host_properties),
            Remediation::Debug(api_call) => api_call.call(host_handler, host_properties),
            Remediation::Ping(api_call) => api_call.call(host_handler, host_properties),
            Remediation::Service(api_call) => api_call.call(host_handler, host_properties),
            Remediation::Command(api_call) => api_call.call(host_handler, host_properties),
        }
    }

    pub fn display(&self) -> String {
        match self {
            Remediation::None(s) => format!("None({})", s),
            Remediation::Pacman(api_call) => api_call.display(),
            Remediation::Apt(api_call) => api_call.display(),
            Remediation::YumDnf(api_call) => api_call.display(),
            Remediation::LineInFile(api_call) => api_call.display(),
            Remediation::Debug(api_call) => api_call.display(),
            Remediation::Ping(api_call) => api_call.display(),
            Remediation::Service(api_call) => api_call.display(),
            Remediation::Command(api_call) => api_call.display(),
        }
    }
}