cdk_ansible_core/core/
mod.rs

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    /// Name of the playbook
29    /// The output file name will be `<name>.yaml`
30    pub name: String,
31    pub plays: Vec<Play>,
32}
33
34/// Option for an unset value
35///
36/// This differs from `Option<T>` in that it has a [`OptU::Unset`], not [`None`]
37/// In serializing, [`OptU::Unset`] is skipped, while [`None`] is serialized as `null`.
38///
39/// ```rust
40/// use cdk_ansible_core::core::OptU;
41///
42/// let x: OptU<i32> = OptU::Unset;
43/// ```
44#[derive(Clone, Debug, PartialEq, Default, Serialize)]
45#[serde(untagged)]
46pub enum OptU<T: Serialize> {
47    Some(T),
48    #[default]
49    Unset,
50}
51
52/// For skip_serializing_if of serde
53impl<T: Serialize> OptU<T> {
54    pub fn is_unset(&self) -> bool {
55        matches!(self, OptU::Unset)
56    }
57}
58
59/// Play
60/// Optional Values are defined in [`PlayOptions`]
61#[derive(Serialize, Clone, Debug)]
62pub struct Play {
63    /// Identifier. Can be used for documentation, or in tasks/handlers.
64    pub name: String,
65    /// A list of groups, hosts or host pattern that translates into a list of hosts that are the play's target.
66    pub hosts: StringOrVecString,
67    #[serde(flatten)]
68    pub options: PlayOptions,
69    /// Main list of tasks to execute in the play, they run after roles and before post_tasks.
70    pub tasks: Vec<Task>,
71}
72
73/// [playbook keywords (play)](https://docs.ansible.com/ansible/latest/reference_appendices/playbooks_keywords.html#play)
74#[derive(Serialize, Default, Clone, Debug)]
75pub struct PlayOptions {
76    /// Force any un-handled task errors on any host to propagate to all hosts and end the play.
77    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
78    pub any_errors_fatal: OptU<bool>,
79    /// Boolean that controls if privilege escalation is used or not on Task execution.
80    /// Implemented by the become plugin. See Become plugins.
81    #[serde(
82        rename = "become",
83        default = "OptU::default",
84        skip_serializing_if = "OptU::is_unset"
85    )]
86    pub become_: OptU<bool>,
87    /// Path to the executable used to elevate privileges. Implemented by the become plugin. See Become plugins.
88    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
89    pub become_exe: OptU<String>,
90    /// A string of flag(s) to pass to the privilege escalation program when become is True.
91    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
92    pub become_flags: OptU<String>,
93    /// Which method of privilege escalation to use (such as sudo or su).
94    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
95    pub become_method: OptU<String>,
96    /// User that you 'become' after using privilege escalation. The remote/login user must have permissions to become this user.
97    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
98    pub become_user: OptU<String>,
99    /// A boolean that controls if a task is executed in 'check' mode. See Validating tasks: check mode and diff mode.
100    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
101    pub check_mode: OptU<bool>,
102    /// List of collection namespaces to search for modules, plugins, and roles. See Using collections in a playbook
103    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
104    pub collections: OptU<Vec<String>>,
105    /// Allows you to change the connection plugin used for tasks to execute on the target. See Using connection plugins.
106    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
107    pub connection: OptU<String>,
108    /// Enable debugging tasks based on the state of the task result. See Debugging tasks.
109    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
110    pub debugger: OptU<bool>,
111    /// Toggle to make tasks return 'diff' information or not.
112    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
113    pub diff: OptU<bool>,
114    /// A dictionary that gets converted into environment vars to be provided for the task upon execution.
115    /// This can ONLY be used with modules. This is not supported for any other type of plugins nor Ansible itself nor its configuration,
116    /// it just sets the variables for the code responsible for executing the task.
117    /// This is not a recommended way to pass in confidential data.
118    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
119    pub environment: OptU<IndexMap<String, String>>,
120    /// Set the fact path option for the fact gathering plugin controlled by gather_facts.
121    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
122    pub fact_path: OptU<String>,
123    /// Will force notified handler execution for hosts even if they failed during the play.
124    /// Will not trigger if the play itself fails.
125    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
126    pub force_handlers: OptU<bool>,
127    /// A boolean that controls if the play will automatically run the 'setup' task to gather facts for the hosts.
128    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
129    pub gather_facts: OptU<bool>,
130    /// Allows you to pass subset options to the fact gathering plugin controlled by gather_facts.
131    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
132    pub gather_subset: OptU<Vec<String>>,
133    /// Allows you to set the timeout for the fact gathering plugin controlled by gather_facts.
134    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
135    pub gather_timeout: OptU<i64>,
136    /// A section with tasks that are treated as handlers, these won't get executed normally,
137    /// only when notified after each section of tasks is complete.
138    /// A handler's listen field is not templatable.
139    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
140    pub handlers: OptU<Vec<Task>>,
141    /// Boolean that allows you to ignore task failures and continue with play. It does not affect connection errors.
142    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
143    pub ignore_errors: OptU<bool>,
144    /// Boolean that allows you to ignore task failures due to an unreachable host and continue with the play.
145    /// This does not affect other task errors (see ignore_errors) but is useful for groups of volatile/ephemeral hosts.
146    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
147    pub ignore_unreachable: OptU<bool>,
148    /// Can be used to abort the run after a given percentage of hosts in the current batch has failed.
149    /// This only works on linear or linear-derived strategies.
150    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
151    pub max_fail_percentage: OptU<i64>,
152    /// Specifies default parameter values for modules.
153    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
154    pub module_defaults: OptU<IndexMap<String, serde_json::Value>>,
155    /// Boolean that controls information disclosure.
156    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
157    pub no_log: OptU<bool>,
158    /// Controls the sorting of hosts as they are used for executing the play.
159    /// Possible values are inventory (default), sorted, reverse_sorted, reverse_inventory and shuffle.
160    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
161    pub order: OptU<String>,
162    /// Used to override the default port used in a connection.
163    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
164    pub port: OptU<i64>,
165    /// A list of tasks to execute after the tasks section.
166    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
167    pub post_tasks: OptU<Vec<Task>>,
168    /// A list of tasks to execute before roles.
169    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
170    pub pre_tasks: OptU<Vec<Task>>,
171    /// User used to log into the target via the connection plugin.
172    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
173    pub remote_user: OptU<String>,
174    /// List of roles to be imported into the play
175    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
176    pub roles: OptU<Vec<String>>,
177    /// Boolean that will bypass the host loop, forcing the task to attempt to execute on the first host available
178    /// and afterward apply any results and facts to all active hosts in the same batch.
179    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
180    pub run_once: OptU<bool>,
181    /// Explicitly define how Ansible batches the execution of the current play on the play's target. See Setting the batch size with serial.
182    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
183    pub serial: OptU<i64>,
184    /// Allows you to choose the strategy plugin to use for the play. See Strategy plugins.
185    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
186    pub strategy: OptU<String>,
187    /// Tags applied to the task or included tasks, this allows selecting subsets of tasks from the command line.
188    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
189    pub tags: OptU<Vec<String>>,
190    /// Limit the number of concurrent task runs on task, block and playbook level. This is independent of the forks and serial settings, but cannot be set higher than those limits. For example, if forks is set to 10 and the throttle is set to 15, at most 10 hosts will be operated on in parallel.
191    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
192    pub throttle: OptU<i64>,
193    /// Time limit for the task action to execute in, if exceeded, Ansible will interrupt the process. Timeout does not include templating or looping.
194    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
195    pub timeout: OptU<i64>,
196    /// Dictionary/map of variables
197    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
198    pub vars: OptU<IndexMap<String, serde_json::Value>>,
199    /// List of files that contain vars to include in the play.
200    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
201    pub vars_files: OptU<Vec<String>>,
202    /// List of variables to prompt for.
203    #[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    /// Identifier. Can be used for documentation, or in tasks/handlers.
210    pub name: String,
211    #[serde(flatten)]
212    pub options: TaskOptions,
213    #[serde(flatten)]
214    pub command: Box<dyn TaskModule>,
215}
216
217/// Task module trait
218///
219/// If you want to add a new task module, you need to implement this trait
220/// https://crates.io/crates/erased-serde
221///
222/// ```rust
223/// use cdk_ansible_core::core::TaskModule;
224/// use serde::Serialize;
225///
226/// #[derive(Serialize, Clone, Debug)]
227/// struct SampleTaskModule {
228///     x1: String,
229/// }
230/// impl TaskModule for SampleTaskModule {}
231/// ```
232pub trait TaskModule: erased_serde::Serialize + DynClone + std::fmt::Debug {}
233
234serialize_trait_object!(TaskModule);
235clone_trait_object!(TaskModule);
236
237/// [playbook keyword (task)](https://docs.ansible.com/ansible/latest/reference_appendices/playbooks_keywords.html#task)
238#[derive(Serialize, Default, Clone, Debug, PartialEq)]
239pub struct TaskOptions {
240    /// The 'action' to execute for a task, it normally translates into a C(module) or action plugin.
241    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
242    pub action: OptU<String>,
243    /// Force any un-handled task errors on any host to propagate to all hosts and end the play.
244    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
245    pub any_errors_fatal: OptU<bool>,
246    /// A secondary way to add arguments into a task. Takes a dictionary in which keys map to options and values.
247    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
248    pub args: OptU<IndexMap<String, serde_json::Value>>,
249    /// Run a task asynchronously if the C(action) supports this; the value is the maximum runtime in seconds.
250    #[serde(
251        rename = "async",
252        default = "OptU::default",
253        skip_serializing_if = "OptU::is_unset"
254    )]
255    pub async_: OptU<i64>,
256    /// Boolean that controls if privilege escalation is used or not on Task execution.
257    /// Implemented by the become plugin. See Become plugins.
258    #[serde(
259        rename = "become",
260        default = "OptU::default",
261        skip_serializing_if = "OptU::is_unset"
262    )]
263    pub become_: OptU<bool>,
264    /// Path to the executable used to elevate privileges. Implemented by the become plugin. See Become plugins.
265    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
266    pub become_exe: OptU<String>,
267    /// A string of flag(s) to pass to the privilege escalation program when become is True.
268    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
269    pub become_flags: OptU<String>,
270    /// Which method of privilege escalation to use (such as sudo or su).
271    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
272    pub become_method: OptU<String>,
273    /// User that you 'become' after using privilege escalation. The remote/login user must have permissions to become this user.
274    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
275    pub become_user: OptU<String>,
276    /// Conditional expression that overrides the task's normal 'changed' status.
277    ///
278    /// The ansible original type allows `Array of strings`.
279    /// But we use `String` for now, because all conditions are expressed as a single string.
280    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
281    pub changed_when: OptU<BoolOrStringOrVecString>,
282    /// A boolean that controls if a task is executed in 'check' mode. See Validating tasks: check mode and diff mode.
283    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
284    pub check_mode: OptU<bool>,
285    /// List of collection namespaces to search for modules, plugins, and roles. See Using collections in a playbook
286    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
287    pub collections: OptU<Vec<String>>,
288    /// Allows you to change the connection plugin used for tasks to execute on the target. See Using connection plugins.
289    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
290    pub connection: OptU<String>,
291    /// Enable debugging tasks based on the state of the task result. See Debugging tasks.
292    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
293    pub debugger: OptU<bool>,
294    ///
295    /// delay
296    /// Number of seconds to delay between retries. This setting is only used in combination with until.
297    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
298    pub delay: OptU<i64>,
299    //
300    /// delegate_facts
301    /// Boolean that allows you to apply facts to a delegated host instead of inventory_hostname.
302    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
303    pub delegate_facts: OptU<bool>,
304    /// Host to execute task instead of the target (inventory_hostname).
305    /// Connection vars from the delegated host will also be used for the task.
306    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
307    pub delegate_to: OptU<String>,
308
309    /// Toggle to make tasks return 'diff' information or not.
310    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
311    pub diff: OptU<bool>,
312
313    /// A dictionary that gets converted into environment vars to be provided for the task upon execution.
314    /// This can ONLY be used with modules. This is not supported for any other type of plugins nor Ansible itself nor its configuration,
315    /// it just sets the variables for the code responsible for executing the task.
316    /// This is not a recommended way to pass in confidential data.
317    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
318    pub environment: OptU<IndexMap<String, String>>,
319    /// Conditional expression that overrides the task's normal 'failed' status.
320    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
321    pub failed_when: OptU<BoolOrStringOrVecString>,
322
323    /// Boolean that allows you to ignore task failures and continue with play. It does not affect connection errors.
324    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
325    pub ignore_errors: OptU<bool>,
326
327    /// Boolean that allows you to ignore task failures due to an unreachable host and continue with the play.
328    /// This does not affect other task errors (see ignore_errors) but is useful for groups of volatile/ephemeral hosts.
329    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
330    pub ignore_unreachable: OptU<bool>,
331
332    /// Same as action but also implies delegate_to: localhost
333    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
334    pub local_action: OptU<String>,
335    /// Takes a list for the task to iterate over, saving each list element into the item variable (configurable via loop_control)
336    #[serde(
337        rename = "loop",
338        default = "OptU::default",
339        skip_serializing_if = "OptU::is_unset"
340    )]
341    pub loop_: OptU<Vec<serde_json::Value>>,
342
343    /// Several keys here allow you to modify/set loop behavior in a task. See Adding controls to loops.
344    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
345    pub loop_control: OptU<IndexMap<String, serde_json::Value>>,
346
347    /// Specifies default parameter values for modules.
348    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
349    pub module_defaults: OptU<IndexMap<String, serde_json::Value>>,
350
351    /// Boolean that controls information disclosure.
352    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
353    pub no_log: OptU<bool>,
354    /// List of handlers to notify when the task returns a 'changed=True' status.
355    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
356    pub notify: OptU<Vec<String>>,
357
358    /// Sets the polling interval in seconds for async tasks (default 10s).
359    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
360    pub poll: OptU<i64>,
361
362    /// Used to override the default port used in a connection.
363    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
364    pub port: OptU<i64>,
365
366    /// Name of variable that will contain task status and module return data.
367    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
368    pub register: OptU<String>,
369
370    /// User used to log into the target via the connection plugin.
371    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
372    pub remote_user: OptU<String>,
373
374    /// Number of retries before giving up in a until loop. This setting is only used in combination with until.
375    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
376    pub retries: OptU<i64>,
377    /// Boolean that will bypass the host loop, forcing the task to attempt to execute on the first host available
378    /// and afterward apply any results and facts to all active hosts in the same batch.
379    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
380    pub run_once: OptU<bool>,
381
382    /// Tags applied to the task or included tasks, this allows selecting subsets of tasks from the command line.
383    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
384    pub tags: OptU<Vec<String>>,
385
386    /// Limit the number of concurrent task runs on task, block and playbook level.
387    /// This is independent of the forks and serial settings, but cannot be set higher than those limits.
388    /// For example, if forks is set to 10 and the throttle is set to 15, at most 10 hosts will be operated on in parallel.
389    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
390    pub throttle: OptU<i64>,
391
392    /// Time limit for the task action to execute in, if exceeded, Ansible will interrupt the process.
393    /// Timeout does not include templating or looping.
394    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
395    pub timeout: OptU<i64>,
396
397    /// This keyword implies a 'retries loop' that will go on until the condition supplied here is met or we hit the retries limit.
398    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
399    pub until: OptU<String>,
400
401    /// Dictionary/map of variables
402    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
403    pub vars: OptU<IndexMap<String, serde_json::Value>>,
404
405    /// Conditional expression, determines if an iteration of a task is run or not.
406    #[serde(default = "OptU::default", skip_serializing_if = "OptU::is_unset")]
407    pub when: OptU<BoolOrStringOrVecString>,
408    // FIXME: not supported yet!
409    // with_<lookup_plugin>
410    // The same as loop but magically adds the output of any lookup plugin to generate the item list.
411}
412
413/// A boolean or a string
414#[derive(Serialize, Clone, Debug, PartialEq)]
415#[serde(untagged)]
416pub enum BoolOrString {
417    Bool(bool),
418    String(String),
419}
420
421impl From<bool> for BoolOrString {
422    fn from(value: bool) -> Self {
423        Self::Bool(value)
424    }
425}
426
427impl From<String> for BoolOrString {
428    fn from(value: String) -> Self {
429        Self::String(value)
430    }
431}
432
433/// A string or a vector of strings
434#[derive(Serialize, Clone, Debug, PartialEq)]
435#[serde(untagged)]
436pub enum StringOrVecString {
437    String(String),
438    VecString(Vec<String>),
439}
440
441impl From<String> for StringOrVecString {
442    fn from(value: String) -> Self {
443        Self::String(value)
444    }
445}
446
447impl From<Vec<String>> for StringOrVecString {
448    fn from(value: Vec<String>) -> Self {
449        Self::VecString(value)
450    }
451}
452
453/// A boolean or a string or a vector of strings
454#[derive(Serialize, Clone, Debug, PartialEq)]
455#[serde(untagged)]
456pub enum BoolOrStringOrVecString {
457    Bool(bool),
458    String(String),
459    VecString(Vec<String>),
460}
461
462impl From<bool> for BoolOrStringOrVecString {
463    fn from(value: bool) -> Self {
464        Self::Bool(value)
465    }
466}
467
468impl From<String> for BoolOrStringOrVecString {
469    fn from(value: String) -> Self {
470        Self::String(value)
471    }
472}
473
474impl From<Vec<String>> for BoolOrStringOrVecString {
475    fn from(value: Vec<String>) -> Self {
476        Self::VecString(value)
477    }
478}
479
480#[cfg(test)]
481mod tests {
482    use super::*;
483
484    #[derive(Serialize, Clone, Debug, PartialEq)]
485    struct SampleTaskModule {
486        x1: String,
487    }
488
489    impl TaskModule for SampleTaskModule {}
490
491    #[test]
492    fn test_play_minimum() {
493        assert_eq!(
494            serde_json::to_string(&Play {
495                name: "play1".to_string(),
496                hosts: vec!["host1".to_string()].into(),
497                tasks: vec![Task {
498                    name: "task1".to_string(),
499                    options: TaskOptions::default(),
500                    command: Box::new(SampleTaskModule {
501                        x1: "x1".to_string(),
502                    }),
503                }],
504                options: PlayOptions::default(),
505            })
506            .expect("failed to serialize"),
507            r#"{"name":"play1","hosts":["host1"],"tasks":[{"name":"task1","x1":"x1"}]}"#
508        );
509    }
510
511    #[test]
512    fn test_play_with_all_fields() {
513        assert_eq!(
514            serde_json::to_string(&Play {
515                name: "play1".to_string(),
516                hosts: vec!["host1".to_string()].into(),
517                tasks: vec![Task {
518                    name: "task1".to_string(),
519                    options: TaskOptions::default(),
520                    command: Box::new(SampleTaskModule {
521                        x1: "x1".to_string(),
522                    }),
523                }],
524                options: PlayOptions {
525                    any_errors_fatal: OptU::Some(true),
526                    become_: OptU::Some(true),
527                    become_exe: OptU::Some("become_exe".to_string()),
528                    become_flags: OptU::Some("become_flags".to_string()),
529                    become_method: OptU::Some("become_method".to_string()),
530                    become_user: OptU::Some("become_user".to_string()),
531                    check_mode: OptU::Some(true),
532                    collections: OptU::Some(vec!["collection1".to_string()]),
533                    connection: OptU::Some("connection1".to_string()),
534                    debugger: OptU::Some(true),
535                    diff: OptU::Some(true),
536                    environment: OptU::Some(IndexMap::from([(
537                        "env1".to_string(),
538                        "value1".to_string()
539                    )])),
540                    fact_path: OptU::Some("fact_path".to_string()),
541                    force_handlers: OptU::Some(true),
542                    gather_facts: OptU::Some(true),
543                    gather_subset: OptU::Some(vec!["gather_subset1".to_string()]),
544                    gather_timeout: OptU::Some(10),
545                    handlers: OptU::Some(vec![Task {
546                        name: "handler1".to_string(),
547                        options: TaskOptions::default(),
548                        command: Box::new(SampleTaskModule {
549                            x1: "x1".to_string(),
550                        }),
551                    }]),
552                    ignore_errors: OptU::Some(true),
553                    ignore_unreachable: OptU::Some(true),
554                    max_fail_percentage: OptU::Some(10),
555                    module_defaults: OptU::Some(IndexMap::from([(
556                        "module1".to_string(),
557                        serde_json::Value::String("value1".to_string())
558                    )])),
559                    no_log: OptU::Some(true),
560                    order: OptU::Some("order".to_string()),
561                    port: OptU::Some(10),
562                    post_tasks: OptU::Some(vec![Task {
563                        name: "post_task1".to_string(),
564                        options: TaskOptions::default(),
565                        command: Box::new(SampleTaskModule {
566                            x1: "x1".to_string(),
567                        }),
568                    }]),
569                    pre_tasks: OptU::Some(vec![Task {
570                        name: "pre_task1".to_string(),
571                        options: TaskOptions::default(),
572                        command: Box::new(SampleTaskModule {
573                            x1: "x1".to_string(),
574                        }),
575                    }]),
576                    remote_user: OptU::Some("remote_user".to_string()),
577                    roles: OptU::Some(vec!["role1".to_string()]),
578                    run_once: OptU::Some(true),
579                    serial: OptU::Some(10),
580                    strategy: OptU::Some("strategy".to_string()),
581                    tags: OptU::Some(vec!["tag1".to_string()]),
582                    throttle: OptU::Some(10),
583                    timeout: OptU::Some(10),
584                    vars: OptU::Some(IndexMap::from([(
585                        "var1".to_string(),
586                        serde_json::Value::String("value1".to_string())
587                    )])),
588                    vars_files: OptU::Some(vec!["vars_file1".to_string()]),
589                    vars_prompt: OptU::Some(vec!["vars_prompt1".to_string()]),
590                }
591            })
592            .expect("failed to serialize"),
593            String::new()
594                + "{"
595                + r#""name":"play1","#
596                + r#""hosts":["host1"],"#
597                + r#""any_errors_fatal":true,"#
598                + r#""become":true,"#
599                + r#""become_exe":"become_exe","#
600                + r#""become_flags":"become_flags","#
601                + r#""become_method":"become_method","#
602                + r#""become_user":"become_user","#
603                + r#""check_mode":true,"#
604                + r#""collections":["collection1"],"#
605                + r#""connection":"connection1","#
606                + r#""debugger":true,"#
607                + r#""diff":true,"#
608                + r#""environment":{"env1":"value1"},"#
609                + r#""fact_path":"fact_path","#
610                + r#""force_handlers":true,"#
611                + r#""gather_facts":true,"#
612                + r#""gather_subset":["gather_subset1"],"#
613                + r#""gather_timeout":10,"#
614                + r#""handlers":[{"name":"handler1","x1":"x1"}],"#
615                + r#""ignore_errors":true,"#
616                + r#""ignore_unreachable":true,"#
617                + r#""max_fail_percentage":10,"#
618                + r#""module_defaults":{"module1":"value1"},"#
619                + r#""no_log":true,"#
620                + r#""order":"order","#
621                + r#""port":10,"#
622                + r#""post_tasks":[{"name":"post_task1","x1":"x1"}],"#
623                + r#""pre_tasks":[{"name":"pre_task1","x1":"x1"}],"#
624                + r#""remote_user":"remote_user","#
625                + r#""roles":["role1"],"#
626                + r#""run_once":true,"#
627                + r#""serial":10,"#
628                + r#""strategy":"strategy","#
629                + r#""tags":["tag1"],"#
630                + r#""throttle":10,"#
631                + r#""timeout":10,"#
632                + r#""vars":{"var1":"value1"},"#
633                + r#""vars_files":["vars_file1"],"#
634                + r#""vars_prompt":["vars_prompt1"],"#
635                + r#""tasks":[{"name":"task1","x1":"x1"}]"#
636                + r#"}"#
637        );
638    }
639
640    #[test]
641    /// Test all fields
642    fn test_task_options_with_all_fields() {
643        assert_eq!(
644            serde_json::to_string(&TaskOptions {
645                action: OptU::Some("action1".to_string()),
646                any_errors_fatal: OptU::Some(true),
647                args: OptU::Some(IndexMap::from([(
648                    "arg1".to_string(),
649                    serde_json::Value::String("value1".to_string())
650                )])),
651                async_: OptU::Some(10),
652                become_: OptU::Some(true),
653                become_exe: OptU::Some("become_exe".to_string()),
654                become_flags: OptU::Some("become_flags".to_string()),
655                become_method: OptU::Some("become_method".to_string()),
656                become_user: OptU::Some("become_user".to_string()),
657                changed_when: OptU::Some("changed_when".to_string().into()),
658                check_mode: OptU::Some(true),
659                collections: OptU::Some(vec!["collection1".to_string()]),
660                connection: OptU::Some("connection1".to_string()),
661                debugger: OptU::Some(true),
662                delay: OptU::Some(10),
663                delegate_facts: OptU::Some(true),
664                delegate_to: OptU::Some("delegate_to".to_string()),
665                diff: OptU::Some(true),
666                environment: OptU::Some(IndexMap::from([(
667                    "env1".to_string(),
668                    "value1".to_string()
669                )])),
670                failed_when: OptU::Some("failed_when".to_string().into()),
671                ignore_errors: OptU::Some(true),
672                ignore_unreachable: OptU::Some(true),
673                local_action: OptU::Some("local_action".to_string()),
674                loop_: OptU::Some(vec![serde_json::Value::String("loop1".to_string())]),
675                loop_control: OptU::Some(IndexMap::from([(
676                    "loop_control1".to_string(),
677                    serde_json::Value::String("value1".to_string())
678                )])),
679                module_defaults: OptU::Some(IndexMap::from([(
680                    "module1".to_string(),
681                    serde_json::Value::String("value1".to_string())
682                )])),
683                no_log: OptU::Some(true),
684                notify: OptU::Some(vec!["notify1".to_string()]),
685                poll: OptU::Some(10),
686                port: OptU::Some(10),
687                register: OptU::Some("register".to_string()),
688                remote_user: OptU::Some("remote_user".to_string()),
689                retries: OptU::Some(10),
690                run_once: OptU::Some(true),
691                tags: OptU::Some(vec!["tag1".to_string()]),
692                throttle: OptU::Some(10),
693                timeout: OptU::Some(10),
694                until: OptU::Some("until".to_string()),
695                vars: OptU::Some(IndexMap::from([(
696                    "var1".to_string(),
697                    serde_json::Value::String("value1".to_string())
698                )])),
699                when: OptU::Some("when".to_string().into()),
700            })
701            .expect("failed to serialize"),
702            String::new()
703                + r#"{""#
704                + r#"action":"action1","#
705                + r#""any_errors_fatal":true,"#
706                + r#""args":{"arg1":"value1"},"#
707                + r#""async":10,"#
708                + r#""become":true,"#
709                + r#""become_exe":"become_exe","#
710                + r#""become_flags":"become_flags","#
711                + r#""become_method":"become_method","#
712                + r#""become_user":"become_user","#
713                + r#""changed_when":"changed_when","#
714                + r#""check_mode":true,"#
715                + r#""collections":["collection1"],"#
716                + r#""connection":"connection1","#
717                + r#""debugger":true,"#
718                + r#""delay":10,"#
719                + r#""delegate_facts":true,"#
720                + r#""delegate_to":"delegate_to","#
721                + r#""diff":true,"#
722                + r#""environment":{"env1":"value1"},"#
723                + r#""failed_when":"failed_when","#
724                + r#""ignore_errors":true,"#
725                + r#""ignore_unreachable":true,"#
726                + r#""local_action":"local_action","#
727                + r#""loop":["loop1"],"#
728                + r#""loop_control":{"loop_control1":"value1"},"#
729                + r#""module_defaults":{"module1":"value1"},"#
730                + r#""no_log":true,"#
731                + r#""notify":["notify1"],"#
732                + r#""poll":10,"#
733                + r#""port":10,"#
734                + r#""register":"register","#
735                + r#""remote_user":"remote_user","#
736                + r#""retries":10,"#
737                + r#""run_once":true,"#
738                + r#""tags":["tag1"],"#
739                + r#""throttle":10,"#
740                + r#""timeout":10,"#
741                + r#""until":"until","#
742                + r#""vars":{"var1":"value1"},"#
743                + r#""when":"when""#
744                + r#"}"#
745        );
746    }
747
748    #[test]
749    fn test_changed_when_bool() {
750        assert_eq!(
751            serde_json::to_string(&TaskOptions {
752                changed_when: OptU::Some(true.into()),
753                ..Default::default()
754            })
755            .expect("failed to serialize"),
756            String::new() + r#"{"changed_when":true}"#
757        );
758    }
759
760    #[test]
761    fn test_changed_when_string() {
762        assert_eq!(
763            serde_json::to_string(&TaskOptions {
764                changed_when: OptU::Some("changed_when".to_string().into()),
765                ..Default::default()
766            })
767            .expect("failed to serialize"),
768            String::new() + r#"{"changed_when":"changed_when"}"#
769        );
770    }
771    #[test]
772    fn test_changed_when_vec_string() {
773        assert_eq!(
774            serde_json::to_string(&TaskOptions {
775                changed_when: OptU::Some(
776                    vec!["changed_when1".to_string(), "changed_when2".to_string()].into()
777                ),
778                ..Default::default()
779            })
780            .expect("failed to serialize"),
781            String::new() + r#"{"changed_when":["changed_when1","changed_when2"]}"#
782        );
783    }
784
785    #[test]
786    fn test_failed_when_bool() {
787        assert_eq!(
788            serde_json::to_string(&TaskOptions {
789                failed_when: OptU::Some(true.into()),
790                ..Default::default()
791            })
792            .expect("failed to serialize"),
793            String::new() + r#"{"failed_when":true}"#
794        );
795    }
796
797    #[test]
798    fn test_failed_when_string() {
799        assert_eq!(
800            serde_json::to_string(&TaskOptions {
801                failed_when: OptU::Some("failed_when".to_string().into()),
802                ..Default::default()
803            })
804            .expect("failed to serialize"),
805            String::new() + r#"{"failed_when":"failed_when"}"#
806        );
807    }
808
809    #[test]
810    fn test_failed_when_vec_string() {
811        assert_eq!(
812            serde_json::to_string(&TaskOptions {
813                failed_when: OptU::Some(
814                    vec!["failed_when1".to_string(), "failed_when2".to_string()].into()
815                ),
816                ..Default::default()
817            })
818            .expect("failed to serialize"),
819            String::new() + r#"{"failed_when":["failed_when1","failed_when2"]}"#
820        );
821    }
822
823    #[test]
824    fn test_when_bool() {
825        assert_eq!(
826            serde_json::to_string(&TaskOptions {
827                when: OptU::Some(true.into()),
828                ..Default::default()
829            })
830            .expect("failed to serialize"),
831            String::new() + r#"{"when":true}"#
832        );
833    }
834
835    #[test]
836    fn test_when_string() {
837        assert_eq!(
838            serde_json::to_string(&TaskOptions {
839                when: OptU::Some("1 == 1".to_string().into()),
840                ..Default::default()
841            })
842            .expect("failed to serialize"),
843            String::new() + r#"{"when":"1 == 1"}"#
844        );
845    }
846
847    #[test]
848    fn test_when_vec_string() {
849        assert_eq!(
850            serde_json::to_string(&TaskOptions {
851                when: OptU::Some(vec!["1 == 1".to_string(), "2 == 2".to_string()].into()),
852                ..Default::default()
853            })
854            .expect("failed to serialize"),
855            String::new() + r#"{"when":["1 == 1","2 == 2"]}"#
856        );
857    }
858}