1use dyn_clone::{DynClone, clone_trait_object};
2use erased_serde::serialize_trait_object;
3use indexmap::IndexMap;
4use serde::Serialize;
5#[derive(Default, Debug, Clone, PartialEq)]
6pub struct Inventory {
7 pub name: String,
8 pub root: InventoryRoot,
9}
10
11#[derive(Default, Debug, Clone, PartialEq, Serialize)]
12pub struct InventoryRoot {
13 pub all: Child,
14}
15
16#[derive(Default, Debug, Clone, PartialEq, Serialize)]
17pub struct Child {
18 #[serde(skip_serializing_if = "OptU::is_unset")]
19 pub hosts: OptU<IndexMap<String, Option<IndexMap<String, serde_json::Value>>>>,
20 #[serde(skip_serializing_if = "OptU::is_unset")]
21 pub children: OptU<IndexMap<String, Child>>,
22 #[serde(skip_serializing_if = "OptU::is_unset")]
23 pub vars: OptU<IndexMap<String, serde_json::Value>>,
24}
25
26#[derive(Clone, Debug)]
27pub struct Playbook {
28 pub name: String,
31 pub plays: Vec<Play>,
32}
33
34#[derive(Clone, Debug, PartialEq, Default, Serialize)]
45#[serde(untagged)]
46pub enum OptU<T: Serialize> {
47 Some(T),
48 #[default]
49 Unset,
50}
51
52impl<T: Serialize> OptU<T> {
54 pub fn is_unset(&self) -> bool {
55 matches!(self, OptU::Unset)
56 }
57}
58
59#[derive(Serialize, Clone, Debug)]
62pub struct Play {
63 pub name: String,
65 pub hosts: Vec<String>,
67 #[serde(flatten)]
68 pub options: PlayOptions,
69 pub tasks: Vec<Task>,
71}
72
73#[derive(Serialize, Default, Clone, Debug)]
75pub struct PlayOptions {
76 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
78 pub any_errors_fatal: OptU<bool>,
79 #[serde(
82 rename = "become",
83 default = "OptU::default",
84 skip_serializing_if = "OptU::is_unset"
85 )]
86 pub become_: OptU<bool>,
87 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
89 pub become_exe: OptU<String>,
90 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
92 pub become_flags: OptU<String>,
93 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
95 pub become_method: OptU<String>,
96 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
98 pub become_user: OptU<String>,
99 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
101 pub check_mode: OptU<bool>,
102 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
104 pub collections: OptU<Vec<String>>,
105 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
107 pub connection: OptU<String>,
108 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
110 pub debugger: OptU<bool>,
111 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
113 pub diff: OptU<bool>,
114 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
119 pub environment: OptU<IndexMap<String, String>>,
120 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
122 pub fact_path: OptU<String>,
123 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
126 pub force_handlers: OptU<bool>,
127 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
129 pub gather_facts: OptU<bool>,
130 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
132 pub gather_subset: OptU<Vec<String>>,
133 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
135 pub gather_timeout: OptU<i64>,
136 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
140 pub handlers: OptU<Vec<Task>>,
141 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
143 pub ignore_errors: OptU<bool>,
144 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
147 pub ignore_unreachable: OptU<bool>,
148 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
151 pub max_fail_percentage: OptU<i64>,
152 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
154 pub module_defaults: OptU<IndexMap<String, serde_json::Value>>,
155 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
157 pub no_log: OptU<bool>,
158 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
161 pub order: OptU<String>,
162 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
164 pub port: OptU<i64>,
165 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
167 pub post_tasks: OptU<Vec<Task>>,
168 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
170 pub pre_tasks: OptU<Vec<Task>>,
171 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
173 pub remote_user: OptU<String>,
174 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
176 pub roles: OptU<Vec<String>>,
177 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
180 pub run_once: OptU<bool>,
181 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
183 pub serial: OptU<i64>,
184 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
186 pub strategy: OptU<String>,
187 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
189 pub tags: OptU<Vec<String>>,
190 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
192 pub throttle: OptU<i64>,
193 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
195 pub timeout: OptU<i64>,
196 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
198 pub vars: OptU<IndexMap<String, serde_json::Value>>,
199 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
201 pub vars_files: OptU<Vec<String>>,
202 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
204 pub vars_prompt: OptU<Vec<String>>,
205}
206
207#[derive(Serialize, Clone, Debug)]
208pub struct Task {
209 pub name: String,
211 #[serde(flatten)]
212 pub options: TaskOptions,
213 #[serde(flatten)]
214 pub command: Box<dyn TaskModule>,
215}
216
217pub trait TaskModule: erased_serde::Serialize + DynClone + std::fmt::Debug {}
233
234serialize_trait_object!(TaskModule);
235clone_trait_object!(TaskModule);
236
237#[derive(Serialize, Default, Clone, Debug, PartialEq)]
239pub struct TaskOptions {
240 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
242 pub action: OptU<String>,
243 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
245 pub any_errors_fatal: OptU<bool>,
246 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
248 pub args: OptU<IndexMap<String, serde_json::Value>>,
249 #[serde(
251 rename = "async",
252 default = "OptU::default",
253 skip_serializing_if = "OptU::is_unset"
254 )]
255 pub async_: OptU<i64>,
256 #[serde(
259 rename = "become",
260 default = "OptU::default",
261 skip_serializing_if = "OptU::is_unset"
262 )]
263 pub become_: OptU<bool>,
264 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
266 pub become_exe: OptU<String>,
267 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
269 pub become_flags: OptU<String>,
270 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
272 pub become_method: OptU<String>,
273 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
275 pub become_user: OptU<String>,
276 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
278 pub changed_when: OptU<String>,
279 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
281 pub check_mode: OptU<bool>,
282 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
284 pub collections: OptU<Vec<String>>,
285 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
287 pub connection: OptU<String>,
288 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
290 pub debugger: OptU<bool>,
291 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
295 pub delay: OptU<i64>,
296 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
300 pub delegate_facts: OptU<bool>,
301 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
304 pub delegate_to: OptU<String>,
305
306 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
308 pub diff: OptU<bool>,
309
310 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
315 pub environment: OptU<IndexMap<String, String>>,
316 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
318 pub failed_when: OptU<String>,
319
320 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
322 pub ignore_errors: OptU<bool>,
323
324 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
327 pub ignore_unreachable: OptU<bool>,
328
329 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
331 pub local_action: OptU<String>,
332 #[serde(
334 rename = "loop",
335 default = "OptU::default",
336 skip_serializing_if = "OptU::is_unset"
337 )]
338 pub loop_: OptU<Vec<serde_json::Value>>,
339
340 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
342 pub loop_control: OptU<IndexMap<String, serde_json::Value>>,
343
344 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
346 pub module_defaults: OptU<IndexMap<String, serde_json::Value>>,
347
348 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
350 pub no_log: OptU<bool>,
351 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
353 pub notify: OptU<Vec<String>>,
354
355 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
357 pub poll: OptU<i64>,
358
359 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
361 pub port: OptU<i64>,
362
363 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
365 pub register: OptU<String>,
366
367 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
369 pub remote_user: OptU<String>,
370
371 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
373 pub retries: OptU<i64>,
374 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
377 pub run_once: OptU<bool>,
378
379 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
381 pub tags: OptU<Vec<String>>,
382
383 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
387 pub throttle: OptU<i64>,
388
389 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
392 pub timeout: OptU<i64>,
393
394 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
396 pub until: OptU<String>,
397
398 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
400 pub vars: OptU<IndexMap<String, serde_json::Value>>,
401
402 #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
404 pub when: OptU<String>,
405 }
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413
414 #[derive(Serialize, Clone, Debug, PartialEq)]
415 struct SampleTaskModule {
416 x1: String,
417 }
418
419 impl TaskModule for SampleTaskModule {}
420
421 #[test]
422 fn test_play_minimum() {
423 assert_eq!(
424 serde_json::to_string(&Play {
425 name: "play1".to_string(),
426 hosts: vec!["host1".to_string()],
427 tasks: vec![Task {
428 name: "task1".to_string(),
429 options: TaskOptions::default(),
430 command: Box::new(SampleTaskModule {
431 x1: "x1".to_string(),
432 }),
433 }],
434 options: PlayOptions::default(),
435 })
436 .expect("failed to serialize"),
437 r#"{"name":"play1","hosts":["host1"],"tasks":[{"name":"task1","x1":"x1"}]}"#
438 );
439 }
440
441 #[test]
442 fn test_play_with_all_fields() {
443 assert_eq!(
444 serde_json::to_string(&Play {
445 name: "play1".to_string(),
446 hosts: vec!["host1".to_string()],
447 tasks: vec![Task {
448 name: "task1".to_string(),
449 options: TaskOptions::default(),
450 command: Box::new(SampleTaskModule {
451 x1: "x1".to_string(),
452 }),
453 }],
454 options: PlayOptions {
455 any_errors_fatal: OptU::Some(true),
456 become_: OptU::Some(true),
457 become_exe: OptU::Some("become_exe".to_string()),
458 become_flags: OptU::Some("become_flags".to_string()),
459 become_method: OptU::Some("become_method".to_string()),
460 become_user: OptU::Some("become_user".to_string()),
461 check_mode: OptU::Some(true),
462 collections: OptU::Some(vec!["collection1".to_string()]),
463 connection: OptU::Some("connection1".to_string()),
464 debugger: OptU::Some(true),
465 diff: OptU::Some(true),
466 environment: OptU::Some(IndexMap::from([(
467 "env1".to_string(),
468 "value1".to_string()
469 )])),
470 fact_path: OptU::Some("fact_path".to_string()),
471 force_handlers: OptU::Some(true),
472 gather_facts: OptU::Some(true),
473 gather_subset: OptU::Some(vec!["gather_subset1".to_string()]),
474 gather_timeout: OptU::Some(10),
475 handlers: OptU::Some(vec![Task {
476 name: "handler1".to_string(),
477 options: TaskOptions::default(),
478 command: Box::new(SampleTaskModule {
479 x1: "x1".to_string(),
480 }),
481 }]),
482 ignore_errors: OptU::Some(true),
483 ignore_unreachable: OptU::Some(true),
484 max_fail_percentage: OptU::Some(10),
485 module_defaults: OptU::Some(IndexMap::from([(
486 "module1".to_string(),
487 serde_json::Value::String("value1".to_string())
488 )])),
489 no_log: OptU::Some(true),
490 order: OptU::Some("order".to_string()),
491 port: OptU::Some(10),
492 post_tasks: OptU::Some(vec![Task {
493 name: "post_task1".to_string(),
494 options: TaskOptions::default(),
495 command: Box::new(SampleTaskModule {
496 x1: "x1".to_string(),
497 }),
498 }]),
499 pre_tasks: OptU::Some(vec![Task {
500 name: "pre_task1".to_string(),
501 options: TaskOptions::default(),
502 command: Box::new(SampleTaskModule {
503 x1: "x1".to_string(),
504 }),
505 }]),
506 remote_user: OptU::Some("remote_user".to_string()),
507 roles: OptU::Some(vec!["role1".to_string()]),
508 run_once: OptU::Some(true),
509 serial: OptU::Some(10),
510 strategy: OptU::Some("strategy".to_string()),
511 tags: OptU::Some(vec!["tag1".to_string()]),
512 throttle: OptU::Some(10),
513 timeout: OptU::Some(10),
514 vars: OptU::Some(IndexMap::from([(
515 "var1".to_string(),
516 serde_json::Value::String("value1".to_string())
517 )])),
518 vars_files: OptU::Some(vec!["vars_file1".to_string()]),
519 vars_prompt: OptU::Some(vec!["vars_prompt1".to_string()]),
520 }
521 })
522 .expect("failed to serialize"),
523 String::new()
524 + "{"
525 + r#""name":"play1","#
526 + r#""hosts":["host1"],"#
527 + r#""any_errors_fatal":true,"#
528 + r#""become":true,"#
529 + r#""become_exe":"become_exe","#
530 + r#""become_flags":"become_flags","#
531 + r#""become_method":"become_method","#
532 + r#""become_user":"become_user","#
533 + r#""check_mode":true,"#
534 + r#""collections":["collection1"],"#
535 + r#""connection":"connection1","#
536 + r#""debugger":true,"#
537 + r#""diff":true,"#
538 + r#""environment":{"env1":"value1"},"#
539 + r#""fact_path":"fact_path","#
540 + r#""force_handlers":true,"#
541 + r#""gather_facts":true,"#
542 + r#""gather_subset":["gather_subset1"],"#
543 + r#""gather_timeout":10,"#
544 + r#""handlers":[{"name":"handler1","x1":"x1"}],"#
545 + r#""ignore_errors":true,"#
546 + r#""ignore_unreachable":true,"#
547 + r#""max_fail_percentage":10,"#
548 + r#""module_defaults":{"module1":"value1"},"#
549 + r#""no_log":true,"#
550 + r#""order":"order","#
551 + r#""port":10,"#
552 + r#""post_tasks":[{"name":"post_task1","x1":"x1"}],"#
553 + r#""pre_tasks":[{"name":"pre_task1","x1":"x1"}],"#
554 + r#""remote_user":"remote_user","#
555 + r#""roles":["role1"],"#
556 + r#""run_once":true,"#
557 + r#""serial":10,"#
558 + r#""strategy":"strategy","#
559 + r#""tags":["tag1"],"#
560 + r#""throttle":10,"#
561 + r#""timeout":10,"#
562 + r#""vars":{"var1":"value1"},"#
563 + r#""vars_files":["vars_file1"],"#
564 + r#""vars_prompt":["vars_prompt1"],"#
565 + r#""tasks":[{"name":"task1","x1":"x1"}]"#
566 + r#"}"#
567 );
568 }
569
570 #[test]
571 fn test_task_options_with_all_fields() {
573 assert_eq!(
574 serde_json::to_string(&TaskOptions {
575 action: OptU::Some("action1".to_string()),
576 any_errors_fatal: OptU::Some(true),
577 args: OptU::Some(IndexMap::from([(
578 "arg1".to_string(),
579 serde_json::Value::String("value1".to_string())
580 )])),
581 async_: OptU::Some(10),
582 become_: OptU::Some(true),
583 become_exe: OptU::Some("become_exe".to_string()),
584 become_flags: OptU::Some("become_flags".to_string()),
585 become_method: OptU::Some("become_method".to_string()),
586 become_user: OptU::Some("become_user".to_string()),
587 changed_when: OptU::Some("changed_when".to_string()),
588 check_mode: OptU::Some(true),
589 collections: OptU::Some(vec!["collection1".to_string()]),
590 connection: OptU::Some("connection1".to_string()),
591 debugger: OptU::Some(true),
592 delay: OptU::Some(10),
593 delegate_facts: OptU::Some(true),
594 delegate_to: OptU::Some("delegate_to".to_string()),
595 diff: OptU::Some(true),
596 environment: OptU::Some(IndexMap::from([(
597 "env1".to_string(),
598 "value1".to_string()
599 )])),
600 failed_when: OptU::Some("failed_when".to_string()),
601 ignore_errors: OptU::Some(true),
602 ignore_unreachable: OptU::Some(true),
603 local_action: OptU::Some("local_action".to_string()),
604 loop_: OptU::Some(vec![serde_json::Value::String("loop1".to_string())]),
605 loop_control: OptU::Some(IndexMap::from([(
606 "loop_control1".to_string(),
607 serde_json::Value::String("value1".to_string())
608 )])),
609 module_defaults: OptU::Some(IndexMap::from([(
610 "module1".to_string(),
611 serde_json::Value::String("value1".to_string())
612 )])),
613 no_log: OptU::Some(true),
614 notify: OptU::Some(vec!["notify1".to_string()]),
615 poll: OptU::Some(10),
616 port: OptU::Some(10),
617 register: OptU::Some("register".to_string()),
618 remote_user: OptU::Some("remote_user".to_string()),
619 retries: OptU::Some(10),
620 run_once: OptU::Some(true),
621 tags: OptU::Some(vec!["tag1".to_string()]),
622 throttle: OptU::Some(10),
623 timeout: OptU::Some(10),
624 until: OptU::Some("until".to_string()),
625 vars: OptU::Some(IndexMap::from([(
626 "var1".to_string(),
627 serde_json::Value::String("value1".to_string())
628 )])),
629 when: OptU::Some("when".to_string()),
630 })
631 .expect("failed to serialize"),
632 String::new()
633 + r#"{""#
634 + r#"action":"action1","#
635 + r#""any_errors_fatal":true,"#
636 + r#""args":{"arg1":"value1"},"#
637 + r#""async":10,"#
638 + r#""become":true,"#
639 + r#""become_exe":"become_exe","#
640 + r#""become_flags":"become_flags","#
641 + r#""become_method":"become_method","#
642 + r#""become_user":"become_user","#
643 + r#""changed_when":"changed_when","#
644 + r#""check_mode":true,"#
645 + r#""collections":["collection1"],"#
646 + r#""connection":"connection1","#
647 + r#""debugger":true,"#
648 + r#""delay":10,"#
649 + r#""delegate_facts":true,"#
650 + r#""delegate_to":"delegate_to","#
651 + r#""diff":true,"#
652 + r#""environment":{"env1":"value1"},"#
653 + r#""failed_when":"failed_when","#
654 + r#""ignore_errors":true,"#
655 + r#""ignore_unreachable":true,"#
656 + r#""local_action":"local_action","#
657 + r#""loop":["loop1"],"#
658 + r#""loop_control":{"loop_control1":"value1"},"#
659 + r#""module_defaults":{"module1":"value1"},"#
660 + r#""no_log":true,"#
661 + r#""notify":["notify1"],"#
662 + r#""poll":10,"#
663 + r#""port":10,"#
664 + r#""register":"register","#
665 + r#""remote_user":"remote_user","#
666 + r#""retries":10,"#
667 + r#""run_once":true,"#
668 + r#""tags":["tag1"],"#
669 + r#""throttle":10,"#
670 + r#""timeout":10,"#
671 + r#""until":"until","#
672 + r#""vars":{"var1":"value1"},"#
673 + r#""when":"when""#
674 + r#"}"#
675 );
676 }
677}