Skip to main content

regent_sdk/state/attribute/
mod.rs

1pub mod package;
2pub mod shell;
3pub mod system;
4pub mod utilities;
5
6use serde::{Deserialize, Serialize};
7use tera::Context;
8
9use crate::error::RegentError;
10use crate::hosts::managed_host::InternalApiCallOutcome;
11use crate::hosts::privilege::Privilege;
12use crate::hosts::properties::HostProperties;
13use crate::secrets::SecretProvidersPool;
14use crate::state::Check;
15use crate::state::attribute::package::apt::AptApiCall;
16use crate::state::attribute::package::apt::AptBlockExpectedState;
17use crate::state::attribute::package::yumdnf::YumDnfApiCall;
18use crate::state::attribute::package::yumdnf::YumDnfBlockExpectedState;
19use crate::state::attribute::shell::command::CommandApiCall;
20use crate::state::attribute::shell::command::CommandBlockExpectedState;
21use crate::state::attribute::system::service::ServiceApiCall;
22use crate::state::attribute::system::service::ServiceBlockExpectedState;
23use crate::state::attribute::utilities::debug::DebugApiCall;
24use crate::state::attribute::utilities::debug::DebugBlockExpectedState;
25use crate::state::attribute::utilities::lineinfile::LineInFileApiCall;
26use crate::state::attribute::utilities::lineinfile::LineInFileBlockExpectedState;
27use crate::state::attribute::utilities::ping::PingApiCall;
28use crate::state::attribute::utilities::ping::PingBlockExpectedState;
29use crate::state::compliance::AttributeComplianceAssessment;
30use crate::state::compliance::AttributeComplianceResult;
31use crate::state::compliance::AttributeComplianceStatus;
32use crate::{
33    hosts::handlers::HostHandler,
34    hosts::managed_host::{AssessCompliance, ReachCompliance},
35    state::attribute::package::pacman::{PacmanApiCall, PacmanBlockExpectedState},
36};
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
39#[serde(rename_all = "PascalCase")]
40pub struct Attribute {
41    pub privilege: Privilege,
42    detail: AttributeDetail,
43    pub name: Option<String>,
44}
45
46impl Attribute {
47    pub fn from(detail: AttributeDetail, privilege: Privilege, name: Option<String>) -> Attribute {
48        Attribute {
49            privilege,
50            detail,
51            name,
52        }
53    }
54
55    pub fn name(&self) -> String {
56        match self.name {
57            Some(ref name) => name.clone(),
58            None => match self.detail {
59                AttributeDetail::Apt(_) => "Apt".to_string(),
60                AttributeDetail::YumDnf(_) => "YumDnf".to_string(),
61                AttributeDetail::Pacman(_) => "Pacman".to_string(),
62                AttributeDetail::Service(_) => "Service".to_string(),
63                AttributeDetail::Command(_) => "Command".to_string(),
64                AttributeDetail::LineInFile(_) => "LineInFile".to_string(),
65                AttributeDetail::Ping(_) => "Ping".to_string(),
66                AttributeDetail::Debug(_) => "Debug".to_string(),
67            },
68        }
69    }
70
71    pub fn consider_context(&self, context: &Context) -> Result<Attribute, RegentError> {
72        // To have the template engine work, we serialize the Attribute, run the template engine, then deserialize
73        // TODO : is the best way ?
74
75        // Making use of template engine to consider dynamic variables (HostVars, GlobalVars...)
76        let serialized_self = match serde_json::to_string(self) {
77            Ok(serialized_self) => serialized_self,
78            Err(details) => {
79                // Shall never happen as self implements the Serialize trait
80                return Err(RegentError::InternalLogicError(format!(
81                    "Attribute tried to serialize self but failed : {}",
82                    details
83                )));
84            }
85        };
86
87        let context_wise_serialized_self =
88            match tera::Tera::one_off(serialized_self.as_str(), context, true) {
89                Ok(context_aware_attribute) => context_aware_attribute,
90                Err(details) => {
91                    return Err(RegentError::FailureToConsiderContext(format!(
92                        "Failed to consider dynamic context : {}",
93                        details
94                    )));
95                }
96            };
97        match serde_json::from_str::<Attribute>(&context_wise_serialized_self) {
98            Ok(context_aware_attribute) => Ok(context_aware_attribute),
99            Err(detail) => Err(RegentError::FailureToConsiderContext(format!("{}", detail))),
100        }
101    }
102
103    /// 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).
104    pub async fn assess<Handler: HostHandler>(
105        &self,
106        host_handler: &mut Handler,
107        host_properties: &Option<HostProperties>,
108        optional_secret_provider: &Option<SecretProvidersPool>,
109    ) -> Result<AttributeComplianceAssessment, RegentError> {
110        self.detail
111            .assess(
112                host_handler,
113                host_properties,
114                &self.privilege,
115                optional_secret_provider,
116            )
117            .await
118    }
119
120    pub async fn reach_compliance<Handler: HostHandler>(
121        &self,
122        host_handler: &mut Handler,
123        host_properties: &Option<HostProperties>,
124        optional_secret_provider: &Option<SecretProvidersPool>,
125    ) -> Result<AttributeComplianceResult, RegentError> {
126        self.detail
127            .reach_compliance(
128                host_handler,
129                host_properties,
130                &self.privilege,
131                optional_secret_provider,
132            )
133            .await
134    }
135
136    pub fn check(&self) -> Result<(), RegentError> {
137        self.detail.check()
138    }
139
140    // Convenience methods for attributes building
141
142    pub fn apt(
143        details: AptBlockExpectedState,
144        privilege: Privilege,
145        name: Option<String>,
146    ) -> Attribute {
147        Attribute::from(AttributeDetail::Apt(details), privilege, name)
148    }
149
150    pub fn pacman(
151        details: PacmanBlockExpectedState,
152        privilege: Privilege,
153        name: Option<String>,
154    ) -> Attribute {
155        Attribute::from(AttributeDetail::Pacman(details), privilege, name)
156    }
157
158    pub fn yumdnf(
159        details: YumDnfBlockExpectedState,
160        privilege: Privilege,
161        name: Option<String>,
162    ) -> Attribute {
163        Attribute::from(AttributeDetail::YumDnf(details), privilege, name)
164    }
165
166    pub fn command(
167        details: CommandBlockExpectedState,
168        privilege: Privilege,
169        name: Option<String>,
170    ) -> Attribute {
171        Attribute::from(AttributeDetail::Command(details), privilege, name)
172    }
173
174    pub fn service(
175        details: ServiceBlockExpectedState,
176        privilege: Privilege,
177        name: Option<String>,
178    ) -> Attribute {
179        Attribute::from(AttributeDetail::Service(details), privilege, name)
180    }
181
182    pub fn debug(
183        details: DebugBlockExpectedState,
184        privilege: Privilege,
185        name: Option<String>,
186    ) -> Attribute {
187        Attribute::from(AttributeDetail::Debug(details), privilege, name)
188    }
189
190    pub fn lineinfile(
191        details: LineInFileBlockExpectedState,
192        privilege: Privilege,
193        name: Option<String>,
194    ) -> Attribute {
195        Attribute::from(AttributeDetail::LineInFile(details), privilege, name)
196    }
197
198    pub fn ping(
199        details: PingBlockExpectedState,
200        privilege: Privilege,
201        name: Option<String>,
202    ) -> Attribute {
203        Attribute::from(AttributeDetail::Ping(details), privilege, name)
204    }
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize)]
208#[serde(rename_all = "PascalCase")]
209pub enum AttributeDetail {
210    Apt(AptBlockExpectedState),
211    YumDnf(YumDnfBlockExpectedState),
212    Pacman(PacmanBlockExpectedState),
213    LineInFile(LineInFileBlockExpectedState),
214    Debug(DebugBlockExpectedState),
215    Ping(PingBlockExpectedState),
216    Service(ServiceBlockExpectedState),
217    Command(CommandBlockExpectedState),
218}
219
220impl AttributeDetail {
221    pub async fn assess<Handler: HostHandler>(
222        &self,
223        host_handler: &mut Handler,
224        host_properties: &Option<HostProperties>,
225        privilege: &Privilege,
226        optional_secret_provider: &Option<SecretProvidersPool>,
227    ) -> Result<AttributeComplianceAssessment, RegentError> {
228        match self {
229            AttributeDetail::Apt(expected_state_criteria) => {
230                expected_state_criteria
231                    .assess_compliance(
232                        host_handler,
233                        host_properties,
234                        privilege,
235                        optional_secret_provider,
236                    )
237                    .await
238            }
239            AttributeDetail::YumDnf(expected_state_criteria) => {
240                expected_state_criteria
241                    .assess_compliance(
242                        host_handler,
243                        host_properties,
244                        privilege,
245                        optional_secret_provider,
246                    )
247                    .await
248            }
249            AttributeDetail::Pacman(expected_state_criteria) => {
250                expected_state_criteria
251                    .assess_compliance(
252                        host_handler,
253                        host_properties,
254                        privilege,
255                        optional_secret_provider,
256                    )
257                    .await
258            }
259            AttributeDetail::LineInFile(expected_state_criteria) => {
260                expected_state_criteria
261                    .assess_compliance(
262                        host_handler,
263                        host_properties,
264                        privilege,
265                        optional_secret_provider,
266                    )
267                    .await
268            }
269            AttributeDetail::Debug(expected_state_criteria) => {
270                expected_state_criteria
271                    .assess_compliance(
272                        host_handler,
273                        host_properties,
274                        privilege,
275                        optional_secret_provider,
276                    )
277                    .await
278            }
279            AttributeDetail::Ping(expected_state_criteria) => {
280                expected_state_criteria
281                    .assess_compliance(
282                        host_handler,
283                        host_properties,
284                        privilege,
285                        optional_secret_provider,
286                    )
287                    .await
288            }
289            AttributeDetail::Service(expected_state_criteria) => {
290                expected_state_criteria
291                    .assess_compliance(
292                        host_handler,
293                        host_properties,
294                        privilege,
295                        optional_secret_provider,
296                    )
297                    .await
298            }
299            AttributeDetail::Command(expected_state_criteria) => {
300                expected_state_criteria
301                    .assess_compliance(
302                        host_handler,
303                        host_properties,
304                        privilege,
305                        optional_secret_provider,
306                    )
307                    .await
308            }
309        }
310    }
311
312    pub async fn reach_compliance<Handler: HostHandler>(
313        &self,
314        host_handler: &mut Handler,
315        host_properties: &Option<HostProperties>,
316        privilege: &Privilege,
317        optional_secret_provider: &Option<SecretProvidersPool>,
318    ) -> Result<AttributeComplianceResult, RegentError> {
319        match self
320            .assess(
321                host_handler,
322                host_properties,
323                privilege,
324                optional_secret_provider,
325            )
326            .await
327        {
328            Ok(attribute_compliance) => match attribute_compliance {
329                AttributeComplianceAssessment::Compliant => Ok(AttributeComplianceResult::from(
330                    AttributeComplianceStatus::AlreadyCompliant,
331                    None,
332                )),
333                AttributeComplianceAssessment::NonCompliant(remediations) => {
334                    if remediations.len() == 0 {
335                        return Err(RegentError::InternalLogicError(format!(
336                            "This should not have been called as the ManagedHost is already compliant"
337                        )));
338                    }
339
340                    let mut actions_taken: Vec<(Remediation, InternalApiCallOutcome)> = Vec::new();
341
342                    for remediation in remediations {
343                        let (remediation, internal_api_call_outcome) = match &remediation {
344                            Remediation::None(message) => {
345                                return Err(RegentError::InternalLogicError(format!(
346                                    "Remediation::None({}) : get rid of this",
347                                    message
348                                )));
349                            }
350                            Remediation::Pacman(attribute_api_call) => match attribute_api_call
351                                .call(host_handler, host_properties, optional_secret_provider)
352                                .await
353                            {
354                                Ok(internal_api_call_outcome) => {
355                                    (remediation, internal_api_call_outcome)
356                                }
357                                Err(details) => {
358                                    return Err(details);
359                                }
360                            },
361                            Remediation::Apt(attribute_api_call) => {
362                                match attribute_api_call
363                                    .call(host_handler, host_properties, optional_secret_provider)
364                                    .await
365                                {
366                                    Ok(internal_api_call_outcome) => {
367                                        (remediation, internal_api_call_outcome)
368                                    }
369                                    Err(details) => {
370                                        return Err(details);
371                                    }
372                                }
373                            }
374                            Remediation::YumDnf(attribute_api_call) => match attribute_api_call
375                                .call(host_handler, host_properties, optional_secret_provider)
376                                .await
377                            {
378                                Ok(internal_api_call_outcome) => {
379                                    (remediation, internal_api_call_outcome)
380                                }
381                                Err(details) => {
382                                    return Err(details);
383                                }
384                            },
385                            Remediation::LineInFile(attribute_api_call) => match attribute_api_call
386                                .call(host_handler, host_properties, optional_secret_provider)
387                                .await
388                            {
389                                Ok(internal_api_call_outcome) => {
390                                    (remediation, internal_api_call_outcome)
391                                }
392                                Err(details) => {
393                                    return Err(details);
394                                }
395                            },
396                            Remediation::Debug(attribute_api_call) => {
397                                match attribute_api_call
398                                    .call(host_handler, host_properties, optional_secret_provider)
399                                    .await
400                                {
401                                    Ok(internal_api_call_outcome) => {
402                                        (remediation, internal_api_call_outcome)
403                                    }
404                                    Err(details) => {
405                                        return Err(details);
406                                    }
407                                }
408                            }
409                            Remediation::Ping(attribute_api_call) => {
410                                match attribute_api_call
411                                    .call(host_handler, host_properties, optional_secret_provider)
412                                    .await
413                                {
414                                    Ok(internal_api_call_outcome) => {
415                                        (remediation, internal_api_call_outcome)
416                                    }
417                                    Err(details) => {
418                                        return Err(details);
419                                    }
420                                }
421                            }
422                            Remediation::Service(attribute_api_call) => {
423                                match attribute_api_call
424                                    .call(host_handler, host_properties, optional_secret_provider)
425                                    .await
426                                {
427                                    Ok(internal_api_call_outcome) => {
428                                        (remediation, internal_api_call_outcome)
429                                    }
430                                    Err(details) => {
431                                        return Err(details);
432                                    }
433                                }
434                            }
435                            Remediation::Command(attribute_api_call) => {
436                                match attribute_api_call
437                                    .call(host_handler, host_properties, optional_secret_provider)
438                                    .await
439                                {
440                                    Ok(internal_api_call_outcome) => {
441                                        (remediation, internal_api_call_outcome)
442                                    }
443                                    Err(details) => {
444                                        return Err(details);
445                                    }
446                                }
447                            }
448                        };
449
450                        actions_taken.push((remediation, internal_api_call_outcome.clone()));
451
452                        if let InternalApiCallOutcome::Failure(_detail) = &internal_api_call_outcome
453                        {
454                            return Ok(AttributeComplianceResult::from(
455                                AttributeComplianceStatus::FailedReachedCompliance,
456                                Some(actions_taken),
457                            ));
458                        }
459                    }
460
461                    Ok(AttributeComplianceResult::from(
462                        AttributeComplianceStatus::ReachedCompliance,
463                        Some(actions_taken),
464                    ))
465                }
466            },
467            Err(details) => Err(details),
468        }
469    }
470
471    pub fn check(&self) -> Result<(), RegentError> {
472        match self {
473            AttributeDetail::Apt(expected_state_block) => expected_state_block.check(),
474            AttributeDetail::YumDnf(expected_state_block) => expected_state_block.check(),
475            AttributeDetail::Pacman(expected_state_block) => expected_state_block.check(),
476            AttributeDetail::LineInFile(expected_state_block) => expected_state_block.check(),
477            AttributeDetail::Debug(expected_state_block) => expected_state_block.check(),
478            AttributeDetail::Ping(expected_state_block) => expected_state_block.check(),
479            AttributeDetail::Service(expected_state_block) => expected_state_block.check(),
480            AttributeDetail::Command(expected_state_block) => expected_state_block.check(),
481        }
482    }
483}
484
485#[derive(Clone, Serialize, Deserialize)]
486pub enum Remediation {
487    None(String),
488    Pacman(PacmanApiCall),
489    Apt(AptApiCall),
490    YumDnf(YumDnfApiCall),
491    LineInFile(LineInFileApiCall),
492    Debug(DebugApiCall),
493    Ping(PingApiCall),
494    Service(ServiceApiCall),
495    Command(CommandApiCall),
496}
497
498impl std::fmt::Debug for Remediation {
499    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
500        match self {
501            Remediation::None(details) => write!(f, "{}", details),
502            Remediation::Pacman(api_call) => write!(f, "{}", api_call.display()),
503            Remediation::Apt(api_call) => write!(f, "{}", api_call.display()),
504            Remediation::YumDnf(api_call) => write!(f, "{}", api_call.display()),
505            Remediation::LineInFile(api_call) => write!(f, "{}", api_call.display()),
506            Remediation::Debug(api_call) => write!(f, "{}", api_call.display()),
507            Remediation::Ping(api_call) => write!(f, "{}", api_call.display()),
508            Remediation::Service(api_call) => write!(f, "{}", api_call.display()),
509            Remediation::Command(api_call) => write!(f, "{}", api_call.display()),
510        }
511    }
512}
513
514impl Remediation {
515    pub async fn reach_compliance<Handler: HostHandler>(
516        &self,
517        host_handler: &mut Handler,
518        host_properties: &Option<HostProperties>,
519        optional_secret_provider: &Option<SecretProvidersPool>,
520    ) -> Result<InternalApiCallOutcome, RegentError> {
521        match self {
522            Remediation::None(_) => {
523                // This case should not occur here according to current logic
524                Err(RegentError::InternalLogicError(String::from(
525                    "Unexpected remediation",
526                )))
527            }
528            Remediation::Pacman(api_call) => {
529                api_call
530                    .call(host_handler, host_properties, optional_secret_provider)
531                    .await
532            }
533            Remediation::Apt(api_call) => {
534                api_call
535                    .call(host_handler, host_properties, optional_secret_provider)
536                    .await
537            }
538            Remediation::YumDnf(api_call) => {
539                api_call
540                    .call(host_handler, host_properties, optional_secret_provider)
541                    .await
542            }
543            Remediation::LineInFile(api_call) => {
544                api_call
545                    .call(host_handler, host_properties, optional_secret_provider)
546                    .await
547            }
548            Remediation::Debug(api_call) => {
549                api_call
550                    .call(host_handler, host_properties, optional_secret_provider)
551                    .await
552            }
553            Remediation::Ping(api_call) => {
554                api_call
555                    .call(host_handler, host_properties, optional_secret_provider)
556                    .await
557            }
558            Remediation::Service(api_call) => {
559                api_call
560                    .call(host_handler, host_properties, optional_secret_provider)
561                    .await
562            }
563            Remediation::Command(api_call) => {
564                api_call
565                    .call(host_handler, host_properties, optional_secret_provider)
566                    .await
567            }
568        }
569    }
570
571    pub fn display(&self) -> String {
572        match self {
573            Remediation::None(s) => format!("None({})", s),
574            Remediation::Pacman(api_call) => api_call.display(),
575            Remediation::Apt(api_call) => api_call.display(),
576            Remediation::YumDnf(api_call) => api_call.display(),
577            Remediation::LineInFile(api_call) => api_call.display(),
578            Remediation::Debug(api_call) => api_call.display(),
579            Remediation::Ping(api_call) => api_call.display(),
580            Remediation::Service(api_call) => api_call.display(),
581            Remediation::Command(api_call) => api_call.display(),
582        }
583    }
584}