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