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