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 let serialized_self = match serde_json::to_string(self) {
77 Ok(serialized_self) => serialized_self,
78 Err(details) => {
79 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 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 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 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}