Skip to main content

regent_sdk/state/attribute/
mod.rs

1pub mod ai;
2pub mod network;
3pub mod package;
4pub mod shell;
5pub mod system;
6pub mod utilities;
7
8use std::time::Duration;
9
10use serde::{Deserialize, Serialize};
11use tera::Context;
12use tokio::time::Instant;
13use tokio::time::{sleep, timeout as tokio_timeout};
14use tracing::{debug, error, info};
15
16use crate::error::RegentError;
17
18/// Timeout configuration for an attribute
19///
20/// This enum ensures that timeout configurations are mutually exclusive at compile time.
21/// Only one timeout source can be specified: a Duration, seconds, or milliseconds.
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23#[serde(untagged)]
24pub enum AttributeTimeout {
25    /// Use the default timeout from the attribute detail
26    Default,
27    /// Use a specific Duration
28    Duration(Duration),
29    /// Use seconds
30    Seconds(u64),
31    /// Use milliseconds
32    Milliseconds(u64),
33}
34
35impl Default for AttributeTimeout {
36    fn default() -> Self {
37        AttributeTimeout::Default
38    }
39}
40use crate::hosts::managed_host::InternalApiCallOutcome;
41use crate::hosts::privilege::Privilege;
42use crate::hosts::properties::HostProperties;
43use crate::secrets::SecretProvidersPool;
44use crate::state::Check;
45use crate::state::attribute::ai::ollama::OllamaApiCall;
46use crate::state::attribute::ai::ollama::OllamaBlockExpectedState;
47use crate::state::attribute::network::iptables::IptablesApiCall;
48use crate::state::attribute::network::iptables::IptablesBlockExpectedState;
49use crate::state::attribute::package::apt::AptApiCall;
50use crate::state::attribute::package::apt::AptBlockExpectedState;
51use crate::state::attribute::package::apt_repo::AptRepoApiCall;
52use crate::state::attribute::package::apt_repo::AptRepoBlockExpectedState;
53use crate::state::attribute::package::dnf_repo::DnfRepoApiCall;
54use crate::state::attribute::package::dnf_repo::DnfRepoBlockExpectedState;
55use crate::state::attribute::package::yumdnf::YumDnfApiCall;
56use crate::state::attribute::package::yumdnf::YumDnfBlockExpectedState;
57use crate::state::attribute::shell::command::CommandApiCall;
58use crate::state::attribute::shell::command::CommandBlockExpectedState;
59use crate::state::attribute::system::cron::CronApiCall;
60use crate::state::attribute::system::cron::CronBlockExpectedState;
61use crate::state::attribute::system::group::GroupApiCall;
62use crate::state::attribute::system::group::GroupBlockExpectedState;
63use crate::state::attribute::system::hostname::HostnameApiCall;
64use crate::state::attribute::system::hostname::HostnameBlockExpectedState;
65use crate::state::attribute::system::service::ServiceApiCall;
66use crate::state::attribute::system::service::ServiceBlockExpectedState;
67use crate::state::attribute::system::user::UserApiCall;
68use crate::state::attribute::system::user::UserBlockExpectedState;
69use crate::state::attribute::utilities::debug::DebugApiCall;
70use crate::state::attribute::utilities::debug::DebugBlockExpectedState;
71use crate::state::attribute::utilities::lineinfile::LineInFileApiCall;
72use crate::state::attribute::utilities::lineinfile::LineInFileBlockExpectedState;
73use crate::state::attribute::utilities::ping::PingApiCall;
74use crate::state::attribute::utilities::ping::PingBlockExpectedState;
75use crate::state::compliance::AttributeComplianceAssessment;
76use crate::state::compliance::AttributeComplianceResult;
77use crate::state::compliance::AttributeComplianceStatus;
78use crate::{
79    hosts::handlers::HostHandler,
80    hosts::managed_host::{AssessCompliance, ReachCompliance, Timeout},
81    state::attribute::package::pacman::{PacmanApiCall, PacmanBlockExpectedState},
82};
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
85#[serde(rename_all = "PascalCase")]
86pub struct Attribute {
87    pub name: Option<String>,
88    pub privilege: Privilege,
89    detail: AttributeDetail,
90    timeout: AttributeTimeout,
91}
92
93impl Attribute {
94    pub fn from(detail: AttributeDetail, privilege: Privilege, name: Option<String>) -> Attribute {
95        Attribute {
96            privilege,
97            detail,
98            name,
99            timeout: AttributeTimeout::Default,
100        }
101    }
102
103    pub fn name(&self) -> String {
104        match self.name {
105            Some(ref name) => name.clone(),
106            None => match self.detail {
107                AttributeDetail::Apt(_) => "Apt".to_string(),
108                AttributeDetail::AptRepo(_) => "AptRepo".to_string(),
109                AttributeDetail::YumDnf(_) => "YumDnf".to_string(),
110                AttributeDetail::DnfRepo(_) => "DnfRepo".to_string(),
111                AttributeDetail::Pacman(_) => "Pacman".to_string(),
112                AttributeDetail::Service(_) => "Service".to_string(),
113                AttributeDetail::Command(_) => "Command".to_string(),
114                AttributeDetail::LineInFile(_) => "LineInFile".to_string(),
115                AttributeDetail::Ping(_) => "Ping".to_string(),
116                AttributeDetail::Debug(_) => "Debug".to_string(),
117                AttributeDetail::User(_) => "User".to_string(),
118                AttributeDetail::Group(_) => "Group".to_string(),
119                AttributeDetail::Cron(_) => "Cron".to_string(),
120                AttributeDetail::Hostname(_) => "Hostname".to_string(),
121                AttributeDetail::Iptables(_) => "Iptables".to_string(),
122                AttributeDetail::Ollama(_) => "Ollama".to_string(),
123            },
124        }
125    }
126
127    pub fn consider_context(&self, context: &Context) -> Result<Attribute, RegentError> {
128        // To have the template engine work, we serialize the Attribute, run the template engine, then deserialize
129        // TODO : is the best way ?
130
131        // Making use of template engine to consider dynamic variables (HostVars, GlobalVars...)
132        let serialized_self = match serde_json::to_string(self) {
133            Ok(serialized_self) => serialized_self,
134            Err(details) => {
135                // Shall never happen as self implements the Serialize trait
136                return Err(RegentError::InternalLogicError(format!(
137                    "Attribute tried to serialize self but failed : {}",
138                    details
139                )));
140            }
141        };
142
143        let context_wise_serialized_self =
144            match tera::Tera::one_off(serialized_self.as_str(), context, true) {
145                Ok(context_aware_attribute) => context_aware_attribute,
146                Err(details) => {
147                    return Err(RegentError::FailureToConsiderContext(format!(
148                        "Failed to consider dynamic context : {}",
149                        details
150                    )));
151                }
152            };
153        match serde_json::from_str::<Attribute>(&context_wise_serialized_self) {
154            Ok(context_aware_attribute) => {
155                // Validate the configuration after template rendering to ensure
156                // that template variables produced valid configuration
157                context_aware_attribute.check().map_err(|e| {
158                    RegentError::FailureToConsiderContext(format!(
159                        "Post-template validation failed: {}",
160                        e
161                    ))
162                })?;
163                Ok(context_aware_attribute)
164            }
165            Err(detail) => Err(RegentError::FailureToConsiderContext(format!("{}", detail))),
166        }
167    }
168
169    /// Result because the assessment might fail. If it succeeds, it will return either None (AKA already compliant) or Some(Vec of Remediation) (AKA what shall be done to reach the expected state).
170    pub async fn assess<Handler: HostHandler>(
171        &self,
172        host_handler: &mut Handler,
173        host_properties: &Option<HostProperties>,
174        optional_secret_provider: &Option<SecretProvidersPool>,
175    ) -> Result<AttributeComplianceAssessment, RegentError> {
176        self.detail
177            .assess(
178                host_handler,
179                host_properties,
180                &self.privilege,
181                optional_secret_provider,
182                self.timeout()?,
183            )
184            .await
185    }
186
187    pub async fn reach_compliance<Handler: HostHandler>(
188        &self,
189        host_handler: &mut Handler,
190        host_properties: &Option<HostProperties>,
191        optional_secret_provider: &Option<SecretProvidersPool>,
192    ) -> Result<AttributeComplianceResult, RegentError> {
193        self.detail
194            .reach_compliance(
195                host_handler,
196                host_properties,
197                &self.privilege,
198                optional_secret_provider,
199                self.timeout()?,
200            )
201            .await
202    }
203
204    pub fn check(&self) -> Result<(), RegentError> {
205        self.detail.check()
206    }
207
208    pub fn timeout(&self) -> Result<Duration, RegentError> {
209        match &self.timeout {
210            AttributeTimeout::Default => Ok(self.detail.default_timeout()),
211            AttributeTimeout::Duration(duration) => Ok(*duration),
212            AttributeTimeout::Seconds(seconds) => Ok(Duration::from_secs(*seconds)),
213            AttributeTimeout::Milliseconds(milliseconds) => {
214                Ok(Duration::from_millis(*milliseconds))
215            }
216        }
217    }
218
219    // Convenience methods for attributes building
220
221    /// Set a timeout using a Duration
222    pub fn with_timeout(mut self, user_defined_timeout: Duration) -> Self {
223        self.timeout = AttributeTimeout::Duration(user_defined_timeout);
224        self
225    }
226
227    /// Set a timeout using seconds
228    pub fn with_timeout_secs(mut self, seconds: u64) -> Self {
229        self.timeout = AttributeTimeout::Seconds(seconds);
230        self
231    }
232
233    /// Set a timeout using milliseconds
234    pub fn with_timeout_millis(mut self, milliseconds: u64) -> Self {
235        self.timeout = AttributeTimeout::Milliseconds(milliseconds);
236        self
237    }
238
239    pub fn apt(
240        details: AptBlockExpectedState,
241        privilege: Privilege,
242        name: Option<String>,
243    ) -> Attribute {
244        Attribute::from(AttributeDetail::Apt(details), privilege, name)
245    }
246
247    pub fn pacman(
248        details: PacmanBlockExpectedState,
249        privilege: Privilege,
250        name: Option<String>,
251    ) -> Attribute {
252        Attribute::from(AttributeDetail::Pacman(details), privilege, name)
253    }
254
255    pub fn yumdnf(
256        details: YumDnfBlockExpectedState,
257        privilege: Privilege,
258        name: Option<String>,
259    ) -> Attribute {
260        Attribute::from(AttributeDetail::YumDnf(details), privilege, name)
261    }
262
263    pub fn command(
264        details: CommandBlockExpectedState,
265        privilege: Privilege,
266        name: Option<String>,
267    ) -> Attribute {
268        Attribute::from(AttributeDetail::Command(details), privilege, name)
269    }
270
271    pub fn service(
272        details: ServiceBlockExpectedState,
273        privilege: Privilege,
274        name: Option<String>,
275    ) -> Attribute {
276        Attribute::from(AttributeDetail::Service(details), privilege, name)
277    }
278
279    pub fn debug(
280        details: DebugBlockExpectedState,
281        privilege: Privilege,
282        name: Option<String>,
283    ) -> Attribute {
284        Attribute::from(AttributeDetail::Debug(details), privilege, name)
285    }
286
287    pub fn lineinfile(
288        details: LineInFileBlockExpectedState,
289        privilege: Privilege,
290        name: Option<String>,
291    ) -> Attribute {
292        Attribute::from(AttributeDetail::LineInFile(details), privilege, name)
293    }
294
295    pub fn ping(
296        details: PingBlockExpectedState,
297        privilege: Privilege,
298        name: Option<String>,
299    ) -> Attribute {
300        Attribute::from(AttributeDetail::Ping(details), privilege, name)
301    }
302
303    pub fn user(
304        details: UserBlockExpectedState,
305        privilege: Privilege,
306        name: Option<String>,
307    ) -> Attribute {
308        Attribute::from(AttributeDetail::User(details), privilege, name)
309    }
310
311    pub fn group(
312        details: GroupBlockExpectedState,
313        privilege: Privilege,
314        name: Option<String>,
315    ) -> Attribute {
316        Attribute::from(AttributeDetail::Group(details), privilege, name)
317    }
318
319    pub fn cron(
320        details: CronBlockExpectedState,
321        privilege: Privilege,
322        name: Option<String>,
323    ) -> Attribute {
324        Attribute::from(AttributeDetail::Cron(details), privilege, name)
325    }
326
327    pub fn hostname(
328        details: HostnameBlockExpectedState,
329        privilege: Privilege,
330        name: Option<String>,
331    ) -> Attribute {
332        Attribute::from(AttributeDetail::Hostname(details), privilege, name)
333    }
334
335    pub fn apt_repo(
336        details: AptRepoBlockExpectedState,
337        privilege: Privilege,
338        name: Option<String>,
339    ) -> Attribute {
340        Attribute::from(AttributeDetail::AptRepo(details), privilege, name)
341    }
342
343    pub fn dnf_repo(
344        details: DnfRepoBlockExpectedState,
345        privilege: Privilege,
346        name: Option<String>,
347    ) -> Attribute {
348        Attribute::from(AttributeDetail::DnfRepo(details), privilege, name)
349    }
350
351    pub fn iptables(
352        details: IptablesBlockExpectedState,
353        privilege: Privilege,
354        name: Option<String>,
355    ) -> Attribute {
356        Attribute::from(AttributeDetail::Iptables(details), privilege, name)
357    }
358
359    pub fn ollama(
360        details: OllamaBlockExpectedState,
361        privilege: Privilege,
362        name: Option<String>,
363    ) -> Attribute {
364        Attribute::from(AttributeDetail::Ollama(details), privilege, name) // Used 'timeout' in 'from' method
365    }
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize)]
369#[serde(rename_all = "PascalCase")]
370pub enum AttributeDetail {
371    Apt(AptBlockExpectedState),
372    AptRepo(AptRepoBlockExpectedState),
373    YumDnf(YumDnfBlockExpectedState),
374    DnfRepo(DnfRepoBlockExpectedState),
375    Pacman(PacmanBlockExpectedState),
376    LineInFile(LineInFileBlockExpectedState),
377    Debug(DebugBlockExpectedState),
378    Ping(PingBlockExpectedState),
379    Service(ServiceBlockExpectedState),
380    Command(CommandBlockExpectedState),
381    User(UserBlockExpectedState),
382    Group(GroupBlockExpectedState),
383    Cron(CronBlockExpectedState),
384    Hostname(HostnameBlockExpectedState),
385    Iptables(IptablesBlockExpectedState),
386    Ollama(OllamaBlockExpectedState),
387}
388
389impl AttributeDetail {
390    pub fn default_timeout(&self) -> Duration {
391        match self {
392            AttributeDetail::Apt(details) => details.default_timeout(),
393            AttributeDetail::AptRepo(details) => details.default_timeout(),
394            AttributeDetail::YumDnf(details) => details.default_timeout(),
395            AttributeDetail::DnfRepo(details) => details.default_timeout(),
396            AttributeDetail::Pacman(details) => details.default_timeout(),
397            AttributeDetail::LineInFile(details) => details.default_timeout(),
398            AttributeDetail::Debug(details) => details.default_timeout(),
399            AttributeDetail::Ping(details) => details.default_timeout(),
400            AttributeDetail::Service(details) => details.default_timeout(),
401            AttributeDetail::Command(details) => details.default_timeout(),
402            AttributeDetail::User(details) => details.default_timeout(),
403            AttributeDetail::Group(details) => details.default_timeout(),
404            AttributeDetail::Cron(details) => details.default_timeout(),
405            AttributeDetail::Hostname(details) => details.default_timeout(),
406            AttributeDetail::Iptables(details) => details.default_timeout(),
407            AttributeDetail::Ollama(details) => details.default_timeout(),
408        }
409    }
410
411    pub async fn assess<Handler: HostHandler>(
412        &self,
413        host_handler: &mut Handler,
414        host_properties: &Option<HostProperties>,
415        privilege: &Privilege,
416        optional_secret_provider: &Option<SecretProvidersPool>,
417        timeout_duration: Duration,
418    ) -> Result<AttributeComplianceAssessment, RegentError> {
419        match tokio_timeout(
420            timeout_duration,
421            self.raw_assess(
422                host_handler,
423                host_properties,
424                privilege,
425                optional_secret_provider,
426            ),
427        )
428        .await
429        {
430            Ok(raw_assesment_result) => raw_assesment_result,
431            Err(_details) => {
432                error!(timeout = ?timeout_duration, "Timeout elapsed");
433                return Err(RegentError::TimeOutReached(format!(
434                    "Timeout elapsed ({} ms) trying to assess compliance",
435                    timeout_duration.as_millis()
436                )));
437            }
438        }
439    }
440
441    pub async fn raw_assess<Handler: HostHandler>(
442        &self,
443        host_handler: &mut Handler,
444        host_properties: &Option<HostProperties>,
445        privilege: &Privilege,
446        optional_secret_provider: &Option<SecretProvidersPool>,
447    ) -> Result<AttributeComplianceAssessment, RegentError> {
448        match self {
449            AttributeDetail::Apt(expected_state_criteria) => {
450                expected_state_criteria
451                    .assess_compliance(
452                        host_handler,
453                        host_properties,
454                        privilege,
455                        optional_secret_provider,
456                    )
457                    .await
458            }
459            AttributeDetail::AptRepo(expected_state_criteria) => {
460                expected_state_criteria
461                    .assess_compliance(
462                        host_handler,
463                        host_properties,
464                        privilege,
465                        optional_secret_provider,
466                    )
467                    .await
468            }
469            AttributeDetail::YumDnf(expected_state_criteria) => {
470                expected_state_criteria
471                    .assess_compliance(
472                        host_handler,
473                        host_properties,
474                        privilege,
475                        optional_secret_provider,
476                    )
477                    .await
478            }
479            AttributeDetail::DnfRepo(expected_state_criteria) => {
480                expected_state_criteria
481                    .assess_compliance(
482                        host_handler,
483                        host_properties,
484                        privilege,
485                        optional_secret_provider,
486                    )
487                    .await
488            }
489            AttributeDetail::Pacman(expected_state_criteria) => {
490                expected_state_criteria
491                    .assess_compliance(
492                        host_handler,
493                        host_properties,
494                        privilege,
495                        optional_secret_provider,
496                    )
497                    .await
498            }
499            AttributeDetail::LineInFile(expected_state_criteria) => {
500                expected_state_criteria
501                    .assess_compliance(
502                        host_handler,
503                        host_properties,
504                        privilege,
505                        optional_secret_provider,
506                    )
507                    .await
508            }
509            AttributeDetail::Debug(expected_state_criteria) => {
510                expected_state_criteria
511                    .assess_compliance(
512                        host_handler,
513                        host_properties,
514                        privilege,
515                        optional_secret_provider,
516                    )
517                    .await
518            }
519            AttributeDetail::Ping(expected_state_criteria) => {
520                expected_state_criteria
521                    .assess_compliance(
522                        host_handler,
523                        host_properties,
524                        privilege,
525                        optional_secret_provider,
526                    )
527                    .await
528            }
529            AttributeDetail::Service(expected_state_criteria) => {
530                expected_state_criteria
531                    .assess_compliance(
532                        host_handler,
533                        host_properties,
534                        privilege,
535                        optional_secret_provider,
536                    )
537                    .await
538            }
539            AttributeDetail::Command(expected_state_criteria) => {
540                expected_state_criteria
541                    .assess_compliance(
542                        host_handler,
543                        host_properties,
544                        privilege,
545                        optional_secret_provider,
546                    )
547                    .await
548            }
549            AttributeDetail::User(expected_state_criteria) => {
550                expected_state_criteria
551                    .assess_compliance(
552                        host_handler,
553                        host_properties,
554                        privilege,
555                        optional_secret_provider,
556                    )
557                    .await
558            }
559            AttributeDetail::Group(expected_state_criteria) => {
560                expected_state_criteria
561                    .assess_compliance(
562                        host_handler,
563                        host_properties,
564                        privilege,
565                        optional_secret_provider,
566                    )
567                    .await
568            }
569            AttributeDetail::Cron(expected_state_criteria) => {
570                expected_state_criteria
571                    .assess_compliance(
572                        host_handler,
573                        host_properties,
574                        privilege,
575                        optional_secret_provider,
576                    )
577                    .await
578            }
579            AttributeDetail::Hostname(expected_state_criteria) => {
580                expected_state_criteria
581                    .assess_compliance(
582                        host_handler,
583                        host_properties,
584                        privilege,
585                        optional_secret_provider,
586                    )
587                    .await
588            }
589            AttributeDetail::Iptables(expected_state_criteria) => {
590                expected_state_criteria
591                    .assess_compliance(
592                        host_handler,
593                        host_properties,
594                        privilege,
595                        optional_secret_provider,
596                    )
597                    .await
598            }
599            AttributeDetail::Ollama(expected_state_criteria) => {
600                expected_state_criteria
601                    .assess_compliance(
602                        host_handler,
603                        host_properties,
604                        privilege,
605                        optional_secret_provider,
606                    )
607                    .await
608            }
609        }
610    }
611
612    pub async fn reach_compliance<Handler: HostHandler>(
613        &self,
614        host_handler: &mut Handler,
615        host_properties: &Option<HostProperties>,
616        privilege: &Privilege,
617        optional_secret_provider: &Option<SecretProvidersPool>,
618        timeout_duration: Duration,
619    ) -> Result<AttributeComplianceResult, RegentError> {
620        let raw_assesment_result = match tokio_timeout(
621            timeout_duration,
622            self.raw_assess(
623                host_handler,
624                host_properties,
625                privilege,
626                optional_secret_provider,
627            ),
628        )
629        .await
630        {
631            Ok(raw_assesment_result) => raw_assesment_result,
632            Err(_details) => {
633                error!(timeout = ?timeout_duration, "Timeout elapsed");
634                return Err(RegentError::TimeOutReached(format!(
635                    "Timeout elapsed ({} ms) trying to assess compliance",
636                    timeout_duration.as_millis()
637                )));
638            }
639        };
640        match raw_assesment_result {
641            Ok(attribute_compliance) => {
642                info!(timeout = ?timeout_duration, "Start of reach compliance try");
643                match tokio_timeout(
644                    timeout_duration,
645                    self.raw_reach_compliance(
646                        host_handler,
647                        host_properties,
648                        privilege,
649                        optional_secret_provider,
650                        attribute_compliance,
651                    ),
652                )
653                .await
654                {
655                    Ok(raw_compliance_reaching_result) => raw_compliance_reaching_result,
656                    Err(_details) => {
657                        error!(timeout = ?timeout_duration, "Timeout elapsed");
658                        return Err(RegentError::TimeOutReached(format!(
659                            "Timeout elapsed ({} ms) trying to assess compliance",
660                            timeout_duration.as_millis()
661                        )));
662                    }
663                }
664            }
665            Err(details) => Err(details),
666        }
667    }
668
669    pub async fn raw_reach_compliance<Handler: HostHandler>(
670        &self,
671        host_handler: &mut Handler,
672        host_properties: &Option<HostProperties>,
673        privilege: &Privilege,
674        optional_secret_provider: &Option<SecretProvidersPool>,
675        attribute_compliance: AttributeComplianceAssessment,
676    ) -> Result<AttributeComplianceResult, RegentError> {
677        match attribute_compliance {
678            AttributeComplianceAssessment::Compliant => Ok(AttributeComplianceResult::from(
679                AttributeComplianceStatus::AlreadyCompliant,
680                None,
681            )),
682            AttributeComplianceAssessment::NonCompliant(remediations) => {
683                if remediations.is_empty() {
684                    return Err(RegentError::InternalLogicError(format!(
685                        "This should not have been called as the ManagedHost is already compliant"
686                    )));
687                }
688
689                let mut actions_taken: Vec<(Remediation, InternalApiCallOutcome)> = Vec::new();
690
691                for remediation_ref in remediations.iter() {
692                    let (remediation, internal_api_call_outcome) = match remediation_ref {
693                        Remediation::None(message) => {
694                            return Err(RegentError::InternalLogicError(format!(
695                                "Remediation::None({}) : get rid of this",
696                                message
697                            )));
698                        }
699                        Remediation::Pacman(attribute_api_call) => match attribute_api_call
700                            .call(host_handler, host_properties, optional_secret_provider)
701                            .await
702                        {
703                            Ok(internal_api_call_outcome) => {
704                                (remediation_ref.clone(), internal_api_call_outcome)
705                            }
706                            Err(details) => {
707                                return Err(details);
708                            }
709                        },
710                        Remediation::Apt(attribute_api_call) => {
711                            match attribute_api_call
712                                .call(host_handler, host_properties, optional_secret_provider)
713                                .await
714                            {
715                                Ok(internal_api_call_outcome) => {
716                                    (remediation_ref.clone(), internal_api_call_outcome)
717                                }
718                                Err(details) => {
719                                    return Err(details);
720                                }
721                            }
722                        }
723                        Remediation::AptRepo(attribute_api_call) => {
724                            match attribute_api_call
725                                .call(host_handler, host_properties, optional_secret_provider)
726                                .await
727                            {
728                                Ok(internal_api_call_outcome) => {
729                                    (remediation_ref.clone(), internal_api_call_outcome)
730                                }
731                                Err(details) => {
732                                    return Err(details);
733                                }
734                            }
735                        }
736                        Remediation::YumDnf(attribute_api_call) => match attribute_api_call
737                            .call(host_handler, host_properties, optional_secret_provider)
738                            .await
739                        {
740                            Ok(internal_api_call_outcome) => {
741                                (remediation_ref.clone(), internal_api_call_outcome)
742                            }
743                            Err(details) => {
744                                return Err(details);
745                            }
746                        },
747                        Remediation::DnfRepo(attribute_api_call) => match attribute_api_call
748                            .call(host_handler, host_properties, optional_secret_provider)
749                            .await
750                        {
751                            Ok(internal_api_call_outcome) => {
752                                (remediation_ref.clone(), internal_api_call_outcome)
753                            }
754                            Err(details) => {
755                                return Err(details);
756                            }
757                        },
758                        Remediation::LineInFile(attribute_api_call) => match attribute_api_call
759                            .call(host_handler, host_properties, optional_secret_provider)
760                            .await
761                        {
762                            Ok(internal_api_call_outcome) => {
763                                (remediation_ref.clone(), internal_api_call_outcome)
764                            }
765                            Err(details) => {
766                                return Err(details);
767                            }
768                        },
769                        Remediation::Debug(attribute_api_call) => {
770                            match attribute_api_call
771                                .call(host_handler, host_properties, optional_secret_provider)
772                                .await
773                            {
774                                Ok(internal_api_call_outcome) => {
775                                    (remediation_ref.clone(), internal_api_call_outcome)
776                                }
777                                Err(details) => {
778                                    return Err(details);
779                                }
780                            }
781                        }
782                        Remediation::Ping(attribute_api_call) => {
783                            match attribute_api_call
784                                .call(host_handler, host_properties, optional_secret_provider)
785                                .await
786                            {
787                                Ok(internal_api_call_outcome) => {
788                                    (remediation_ref.clone(), internal_api_call_outcome)
789                                }
790                                Err(details) => {
791                                    return Err(details);
792                                }
793                            }
794                        }
795                        Remediation::Service(attribute_api_call) => {
796                            match attribute_api_call
797                                .call(host_handler, host_properties, optional_secret_provider)
798                                .await
799                            {
800                                Ok(internal_api_call_outcome) => {
801                                    (remediation_ref.clone(), internal_api_call_outcome)
802                                }
803                                Err(details) => {
804                                    return Err(details);
805                                }
806                            }
807                        }
808                        Remediation::Command(attribute_api_call) => {
809                            match attribute_api_call
810                                .call(host_handler, host_properties, optional_secret_provider)
811                                .await
812                            {
813                                Ok(internal_api_call_outcome) => {
814                                    (remediation_ref.clone(), internal_api_call_outcome)
815                                }
816                                Err(details) => {
817                                    return Err(details);
818                                }
819                            }
820                        }
821                        Remediation::User(attribute_api_call) => {
822                            match attribute_api_call
823                                .call(host_handler, host_properties, optional_secret_provider)
824                                .await
825                            {
826                                Ok(internal_api_call_outcome) => {
827                                    (remediation_ref.clone(), internal_api_call_outcome)
828                                }
829                                Err(details) => {
830                                    return Err(details);
831                                }
832                            }
833                        }
834                        Remediation::Group(attribute_api_call) => {
835                            match attribute_api_call
836                                .call(host_handler, host_properties, optional_secret_provider)
837                                .await
838                            {
839                                Ok(internal_api_call_outcome) => {
840                                    (remediation_ref.clone(), internal_api_call_outcome)
841                                }
842                                Err(details) => {
843                                    return Err(details);
844                                }
845                            }
846                        }
847                        Remediation::Cron(attribute_api_call) => {
848                            match attribute_api_call
849                                .call(host_handler, host_properties, optional_secret_provider)
850                                .await
851                            {
852                                Ok(internal_api_call_outcome) => {
853                                    (remediation_ref.clone(), internal_api_call_outcome)
854                                }
855                                Err(details) => {
856                                    return Err(details);
857                                }
858                            }
859                        }
860                        Remediation::Hostname(attribute_api_call) => {
861                            match attribute_api_call
862                                .call(host_handler, host_properties, optional_secret_provider)
863                                .await
864                            {
865                                Ok(internal_api_call_outcome) => {
866                                    (remediation_ref.clone(), internal_api_call_outcome)
867                                }
868                                Err(details) => {
869                                    return Err(details);
870                                }
871                            }
872                        }
873                        Remediation::Iptables(attribute_api_call) => {
874                            match attribute_api_call
875                                .call(host_handler, host_properties, optional_secret_provider)
876                                .await
877                            {
878                                Ok(internal_api_call_outcome) => {
879                                    (remediation_ref.clone(), internal_api_call_outcome)
880                                }
881                                Err(details) => {
882                                    return Err(details);
883                                }
884                            }
885                        }
886                        Remediation::Ollama(attribute_api_call) => {
887                            match attribute_api_call
888                                .call(host_handler, host_properties, optional_secret_provider)
889                                .await
890                            {
891                                Ok(internal_api_call_outcome) => {
892                                    (remediation_ref.clone(), internal_api_call_outcome)
893                                }
894                                Err(details) => {
895                                    return Err(details);
896                                }
897                            }
898                        }
899                    };
900
901                    actions_taken.push((remediation, internal_api_call_outcome.clone()));
902
903                    if let InternalApiCallOutcome::Failure(_detail) = &internal_api_call_outcome {
904                        return Ok(AttributeComplianceResult::from(
905                            AttributeComplianceStatus::FailedReachedCompliance,
906                            Some(actions_taken),
907                        ));
908                    }
909                }
910
911                Ok(AttributeComplianceResult::from(
912                    AttributeComplianceStatus::ReachedCompliance,
913                    Some(actions_taken),
914                ))
915            }
916        }
917    }
918
919    pub fn check(&self) -> Result<(), RegentError> {
920        match self {
921            AttributeDetail::Apt(expected_state_block) => expected_state_block.check(),
922            AttributeDetail::AptRepo(expected_state_block) => expected_state_block.check(),
923            AttributeDetail::YumDnf(expected_state_block) => expected_state_block.check(),
924            AttributeDetail::DnfRepo(expected_state_block) => expected_state_block.check(),
925            AttributeDetail::Pacman(expected_state_block) => expected_state_block.check(),
926            AttributeDetail::LineInFile(expected_state_block) => expected_state_block.check(),
927            AttributeDetail::Debug(expected_state_block) => expected_state_block.check(),
928            AttributeDetail::Ping(expected_state_block) => expected_state_block.check(),
929            AttributeDetail::Service(expected_state_block) => expected_state_block.check(),
930            AttributeDetail::Command(expected_state_block) => expected_state_block.check(),
931            AttributeDetail::User(expected_state_block) => expected_state_block.check(),
932            AttributeDetail::Group(expected_state_block) => expected_state_block.check(),
933            AttributeDetail::Cron(expected_state_block) => expected_state_block.check(),
934            AttributeDetail::Hostname(expected_state_block) => expected_state_block.check(),
935            AttributeDetail::Iptables(expected_state_block) => expected_state_block.check(),
936            AttributeDetail::Ollama(expected_state_block) => expected_state_block.check(),
937        }
938    }
939}
940
941#[derive(Clone, Serialize, Deserialize)]
942pub enum Remediation {
943    None(String),
944    Pacman(PacmanApiCall),
945    Apt(AptApiCall),
946    AptRepo(AptRepoApiCall),
947    YumDnf(YumDnfApiCall),
948    DnfRepo(DnfRepoApiCall),
949    LineInFile(LineInFileApiCall),
950    Debug(DebugApiCall),
951    Ping(PingApiCall),
952    Service(ServiceApiCall),
953    Command(CommandApiCall),
954    User(UserApiCall),
955    Group(GroupApiCall),
956    Cron(CronApiCall),
957    Hostname(HostnameApiCall),
958    Iptables(IptablesApiCall),
959    Ollama(OllamaApiCall),
960}
961
962impl std::fmt::Debug for Remediation {
963    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
964        match self {
965            Remediation::None(details) => write!(f, "{}", details),
966            Remediation::Pacman(api_call) => write!(f, "{}", api_call.display()),
967            Remediation::Apt(api_call) => write!(f, "{}", api_call.display()),
968            Remediation::AptRepo(api_call) => write!(f, "{}", api_call.display()),
969            Remediation::YumDnf(api_call) => write!(f, "{}", api_call.display()),
970            Remediation::DnfRepo(api_call) => write!(f, "{}", api_call.display()),
971            Remediation::LineInFile(api_call) => write!(f, "{}", api_call.display()),
972            Remediation::Debug(api_call) => write!(f, "{}", api_call.display()),
973            Remediation::Ping(api_call) => write!(f, "{}", api_call.display()),
974            Remediation::Service(api_call) => write!(f, "{}", api_call.display()),
975            Remediation::Command(api_call) => write!(f, "{}", api_call.display()),
976            Remediation::User(api_call) => write!(f, "{}", api_call.display()),
977            Remediation::Group(api_call) => write!(f, "{}", api_call.display()),
978            Remediation::Cron(api_call) => write!(f, "{}", api_call.display()),
979            Remediation::Hostname(api_call) => write!(f, "{}", api_call.display()),
980            Remediation::Iptables(api_call) => write!(f, "{}", api_call.display()),
981            Remediation::Ollama(api_call) => write!(f, "{}", api_call.display()),
982        }
983    }
984}
985
986impl Remediation {
987    pub async fn reach_compliance<Handler: HostHandler>(
988        &self,
989        host_handler: &mut Handler,
990        host_properties: &Option<HostProperties>,
991        optional_secret_provider: &Option<SecretProvidersPool>,
992        timeout_duration: Duration,
993    ) -> Result<InternalApiCallOutcome, RegentError> {
994        match tokio_timeout(
995            timeout_duration,
996            self.raw_reach_compliance(host_handler, host_properties, optional_secret_provider),
997        )
998        .await
999        {
1000            Ok(raw_assesment_result) => raw_assesment_result,
1001            Err(_details) => {
1002                error!(timeout = ?timeout_duration, "Timeout elapsed");
1003                return Err(RegentError::TimeOutReached(format!(
1004                    "Timeout elapsed ({} ms) trying to run internal API call",
1005                    timeout_duration.as_millis()
1006                )));
1007            }
1008        }
1009    }
1010
1011    pub async fn raw_reach_compliance<Handler: HostHandler>(
1012        &self,
1013        host_handler: &mut Handler,
1014        host_properties: &Option<HostProperties>,
1015        optional_secret_provider: &Option<SecretProvidersPool>,
1016    ) -> Result<InternalApiCallOutcome, RegentError> {
1017        match self {
1018            Remediation::None(_) => {
1019                // This case should not occur here according to current logic
1020                Err(RegentError::InternalLogicError(String::from(
1021                    "Unexpected remediation",
1022                )))
1023            }
1024            Remediation::Pacman(api_call) => {
1025                api_call
1026                    .call(host_handler, host_properties, optional_secret_provider)
1027                    .await
1028            }
1029            Remediation::Apt(api_call) => {
1030                api_call
1031                    .call(host_handler, host_properties, optional_secret_provider)
1032                    .await
1033            }
1034            Remediation::AptRepo(api_call) => {
1035                api_call
1036                    .call(host_handler, host_properties, optional_secret_provider)
1037                    .await
1038            }
1039            Remediation::YumDnf(api_call) => {
1040                api_call
1041                    .call(host_handler, host_properties, optional_secret_provider)
1042                    .await
1043            }
1044            Remediation::DnfRepo(api_call) => {
1045                api_call
1046                    .call(host_handler, host_properties, optional_secret_provider)
1047                    .await
1048            }
1049            Remediation::LineInFile(api_call) => {
1050                api_call
1051                    .call(host_handler, host_properties, optional_secret_provider)
1052                    .await
1053            }
1054            Remediation::Debug(api_call) => {
1055                api_call
1056                    .call(host_handler, host_properties, optional_secret_provider)
1057                    .await
1058            }
1059            Remediation::Ping(api_call) => {
1060                api_call
1061                    .call(host_handler, host_properties, optional_secret_provider)
1062                    .await
1063            }
1064            Remediation::Service(api_call) => {
1065                api_call
1066                    .call(host_handler, host_properties, optional_secret_provider)
1067                    .await
1068            }
1069            Remediation::Command(api_call) => {
1070                api_call
1071                    .call(host_handler, host_properties, optional_secret_provider)
1072                    .await
1073            }
1074            Remediation::User(api_call) => {
1075                api_call
1076                    .call(host_handler, host_properties, optional_secret_provider)
1077                    .await
1078            }
1079            Remediation::Group(api_call) => {
1080                api_call
1081                    .call(host_handler, host_properties, optional_secret_provider)
1082                    .await
1083            }
1084            Remediation::Cron(api_call) => {
1085                api_call
1086                    .call(host_handler, host_properties, optional_secret_provider)
1087                    .await
1088            }
1089            Remediation::Hostname(api_call) => {
1090                api_call
1091                    .call(host_handler, host_properties, optional_secret_provider)
1092                    .await
1093            }
1094            Remediation::Iptables(api_call) => {
1095                api_call
1096                    .call(host_handler, host_properties, optional_secret_provider)
1097                    .await
1098            }
1099            Remediation::Ollama(api_call) => {
1100                api_call
1101                    .call(host_handler, host_properties, optional_secret_provider)
1102                    .await
1103            }
1104        }
1105    }
1106
1107    pub fn display(&self) -> String {
1108        match self {
1109            Remediation::None(s) => format!("None({})", s),
1110            Remediation::Pacman(api_call) => api_call.display(),
1111            Remediation::Apt(api_call) => api_call.display(),
1112            Remediation::AptRepo(api_call) => api_call.display(),
1113            Remediation::YumDnf(api_call) => api_call.display(),
1114            Remediation::DnfRepo(api_call) => api_call.display(),
1115            Remediation::LineInFile(api_call) => api_call.display(),
1116            Remediation::Debug(api_call) => api_call.display(),
1117            Remediation::Ping(api_call) => api_call.display(),
1118            Remediation::Service(api_call) => api_call.display(),
1119            Remediation::Command(api_call) => api_call.display(),
1120            Remediation::User(api_call) => api_call.display(),
1121            Remediation::Group(api_call) => api_call.display(),
1122            Remediation::Cron(api_call) => api_call.display(),
1123            Remediation::Hostname(api_call) => api_call.display(),
1124            Remediation::Iptables(api_call) => api_call.display(),
1125            Remediation::Ollama(api_call) => api_call.display(),
1126        }
1127    }
1128}
1129
1130// This type makes it impossible to have empty remediation lists elsewhere AKA error logic
1131#[derive(Debug, Clone, Serialize, Deserialize)]
1132pub struct RemediationsList {
1133    inner: Vec<Remediation>,
1134}
1135
1136impl RemediationsList {
1137    pub fn from(remediations: Vec<Remediation>) -> Result<RemediationsList, RegentError> {
1138        if remediations.len() == 0 {
1139            Err(RegentError::InternalLogicError(format!(
1140                "Empty remediation list passed"
1141            )))
1142        } else {
1143            Ok(RemediationsList {
1144                inner: remediations,
1145            })
1146        }
1147    }
1148
1149    pub fn remediations(&self) -> &Vec<Remediation> {
1150        &self.inner
1151    }
1152
1153    pub fn into_inner(self) -> Vec<Remediation> {
1154        self.inner
1155    }
1156
1157    pub fn len(&self) -> usize {
1158        self.inner.len()
1159    }
1160
1161    pub fn is_empty(&self) -> bool {
1162        self.inner.is_empty()
1163    }
1164
1165    pub fn iter(&self) -> std::slice::Iter<'_, Remediation> {
1166        self.inner.iter()
1167    }
1168}
1169
1170impl IntoIterator for RemediationsList {
1171    type Item = Remediation;
1172    type IntoIter = std::vec::IntoIter<Remediation>;
1173
1174    fn into_iter(self) -> Self::IntoIter {
1175        self.inner.into_iter()
1176    }
1177}
1178
1179impl<'a> IntoIterator for &'a RemediationsList {
1180    type Item = &'a Remediation;
1181    type IntoIter = std::slice::Iter<'a, Remediation>;
1182
1183    fn into_iter(self) -> Self::IntoIter {
1184        self.inner.iter()
1185    }
1186}
1187
1188impl FromIterator<Remediation> for RemediationsList {
1189    fn from_iter<T: IntoIterator<Item = Remediation>>(iter: T) -> Self {
1190        RemediationsList {
1191            inner: Vec::from_iter(iter),
1192        }
1193    }
1194}