summaryrefslogtreecommitdiffstats
path: root/ansible_collections/lowlydba/sqlserver/tests
diff options
context:
space:
mode:
Diffstat (limited to 'ansible_collections/lowlydba/sqlserver/tests')
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/.ansible-lint10
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/integration_config.sample.yml4
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/inventory2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/inventory.winrm.ci8
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job/tasks/main.yml146
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_category/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_category/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_category/tasks/main.yml69
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_schedule/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_schedule/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_schedule/tasks/main.yml169
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_step/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_step/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_step/tasks/main.yml167
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/backup/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/backup/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/backup/tasks/main.yml25
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/credential/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/credential/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/credential/tasks/main.yml85
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/database/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/database/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/database/tasks/main.yml92
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/dba_multitool/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/dba_multitool/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/dba_multitool/tasks/main.yml37
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/first_responder_kit/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/first_responder_kit/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/first_responder_kit/tasks/main.yml39
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/install_script/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/install_script/files/1-select-choice.sql1
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/install_script/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/install_script/tasks/main.yml61
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/instance_info/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/instance_info/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/instance_info/tasks/main.yml26
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/login/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/login/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/login/tasks/main.yml97
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/maintenance_solution/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/maintenance_solution/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/maintenance_solution/tasks/main.yml29
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/memory/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/memory/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/memory/tasks/main.yml71
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/nonquery/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/nonquery/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/nonquery/tasks/main.yml27
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/resource_governor/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/resource_governor/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/resource_governor/tasks/main.yml36
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_resource_pool/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_resource_pool/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_resource_pool/tasks/main.yml76
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_workload_group/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_workload_group/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_workload_group/tasks/main.yml102
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/sa/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/sa/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/sa/tasks/main.yml66
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver/defaults/main.yml2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver/tasks/main.yml9
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver/vars/main.yml12
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/README.md9
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/connection_plugins/local_pwsh.py197
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/module_utils/Ansible.Basic.cs1481
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/module_utils/Ansible.ModuleUtils.AddType.psm1397
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/shell_plugins/pwsh.py376
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/tasks/main.yml6
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/vars/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_win_sqlserver/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_win_sqlserver/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_win_sqlserver/tasks/main.yml8
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_win_sqlserver/vars/main.yml2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_configure/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_configure/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_configure/tasks/main.yml67
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_whoisactive/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_whoisactive/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_whoisactive/tasks/main.yml46
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/traceflag/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/traceflag/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/traceflag/tasks/main.yml90
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/user/aliases2
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/user/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/user/tasks/main.yml115
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_listener/aliases4
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_listener/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_listener/tasks/main.yml90
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_replica/aliases4
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_replica/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_replica/tasks/main.yml81
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_category/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_category/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_schedule/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_schedule/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_step/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_step/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_availability_group/aliases4
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_availability_group/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_availability_group/tasks/main.yml109
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_backup/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_backup/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_credential/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_credential/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_database/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_database/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_dba_multitool/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_dba_multitool/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_first_responder_kit/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_first_responder_kit/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_hadr/aliases4
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_hadr/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_hadr/tasks/main.yml53
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_install_script/aliases4
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_install_script/files/1-select-choice.sql1
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_install_script/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_install_script/tasks/main.yml72
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_instance_info/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_instance_info/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_login/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_login/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_maintenance_solution/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_maintenance_solution/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_memory/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_memory/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_nonquery/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_nonquery/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_resource_governor/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_resource_governor/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_restore/aliases4
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_restore/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_restore/tasks/main.yml31
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_rg_resource_pool/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_rg_resource_pool/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_rg_workload_group/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_rg_workload_group/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sa/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sa/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sp_configure/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sp_configure/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sp_whoisactive/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sp_whoisactive/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_spn/aliases4
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_spn/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_spn/tasks/main.yml38
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_tcp_port/aliases4
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_tcp_port/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_tcp_port/tasks/main.yml49
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_traceflag/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_traceflag/meta/main.yml3
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_user/aliases5
-rw-r--r--ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_user/meta/main.yml3
157 files changed, 5141 insertions, 0 deletions
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/.ansible-lint b/ansible_collections/lowlydba/sqlserver/tests/integration/.ansible-lint
new file mode 100644
index 00000000..274a4461
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/.ansible-lint
@@ -0,0 +1,10 @@
+# .ansible-lint file for integration tests
+---
+skip_list:
+ - unnamed-task
+ - truthy
+ - var-naming
+ - meta-no-info
+ - ignore-errors
+ - risky-file-permissions
+ - command-instead-of-shell
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/integration_config.sample.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/integration_config.sample.yml
new file mode 100644
index 00000000..dece838f
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/integration_config.sample.yml
@@ -0,0 +1,4 @@
+---
+sqlserver_instance: sqlserver
+sqlserver_username: sa
+sqlserver_password: L0wlydb4
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/inventory b/ansible_collections/lowlydba/sqlserver/tests/integration/inventory
new file mode 100644
index 00000000..7c937f87
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/inventory
@@ -0,0 +1,2 @@
+[testgroup]
+testhost ansible_connection="local" ansible_pipelining="yes" ansible_python_interpreter="/usr/bin/python3"
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/inventory.winrm.ci b/ansible_collections/lowlydba/sqlserver/tests/integration/inventory.winrm.ci
new file mode 100644
index 00000000..ac143737
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/inventory.winrm.ci
@@ -0,0 +1,8 @@
+[windows]
+runner ansible_host=127.0.0.1 ansible_user=admin ansible_password=pass123@
+
+[windows:vars]
+ansible_connection=psrp
+ansible_port=5986
+ansible_psrp_auth=basic
+ansible_psrp_cert_validation=ignore
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job/tasks/main.yml
new file mode 100644
index 00000000..46684e96
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job/tasks/main.yml
@@ -0,0 +1,146 @@
+---
+- name: Var block
+ vars:
+ category_name: "Integration Tests"
+ job_name: "Integration Job"
+ description: "This test is not a test."
+ enabled: true
+ module_defaults:
+ lowlydba.sqlserver.agent_job:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ category: "{{ category_name }}"
+ job: "{{ job_name }}"
+ description: "{{ description }}"
+ force: true
+ enabled: "{{ enabled }}"
+ tags: ["agent_job"]
+ block:
+ # SQL Agent / SMO has delays on returning new data sometimes, and is worse on CI runners -
+ # so explicitly pre-create the category to make sure we get timely & accurate results later
+ - name: Prep agent job category
+ lowlydba.sqlserver.agent_job_category:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ category: "{{ category_name }}"
+
+ - name: Create agent job
+ lowlydba.sqlserver.agent_job:
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.Category == "{{ category_name }}"
+ - result.data.Enabled is true
+ - result.data.Name == "{{ job_name }}"
+ - result.data.OwnerLoginName == "{{ sqlserver_username }}"
+ - result.data.HasSchedule is false
+
+ - name: Create agent job step one
+ lowlydba.sqlserver.agent_job_step:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ job: "{{ job_name }}"
+ step_name: "Step 1"
+ step_id: 1
+
+ - name: Create agent job step two
+ lowlydba.sqlserver.agent_job_step:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ job: "{{ job_name }}"
+ step_name: "Step 2"
+ step_id: 2
+
+ - name: Set start job step id
+ lowlydba.sqlserver.agent_job:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ job: "{{ job_name }}"
+ start_step_id: 2
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result is changed
+
+ - name: No change
+ lowlydba.sqlserver.agent_job:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ job: "{{ job_name }}"
+ start_step_id: 2
+ register: result
+ - assert:
+ that:
+ - result is not changed
+
+ - name: Remove agent job
+ lowlydba.sqlserver.agent_job:
+ state: "absent"
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.Status == "Dropped"
+
+ - name: Create new agent job
+ lowlydba.sqlserver.agent_job:
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.Category == "{{ category_name }}"
+ - result.data.Enabled is true
+ - result.data.OwnerLoginName == "{{ sqlserver_username }}"
+ - result.data.HasSchedule is false
+ - result is changed
+
+ - name: Change agent job
+ lowlydba.sqlserver.agent_job:
+ owner_login: "sa"
+ enabled: false
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.Category == "{{ category_name }}"
+ - result.data.Enabled is false
+ - result.data.Name == "{{ job_name }}"
+ - result.data.OwnerLoginName == "sa"
+ - result.data.HasSchedule is false
+ - result is changed
+
+ - name: Change agent job
+ lowlydba.sqlserver.agent_job:
+ enabled: true
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.Category == "{{ category_name }}"
+ - result.data.Enabled is true
+ - result.data.Name == "{{ job_name }}"
+ - result is changed
+
+ always:
+ - name: Cleanup agent job
+ lowlydba.sqlserver.agent_job:
+ state: "absent"
+
+ - name: Cleanup agent job category
+ lowlydba.sqlserver.agent_job_category:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ category: "{{ category_name }}"
+ state: absent
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_category/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_category/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_category/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_category/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_category/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_category/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_category/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_category/tasks/main.yml
new file mode 100644
index 00000000..739112a4
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_category/tasks/main.yml
@@ -0,0 +1,69 @@
+---
+- name: Var block
+ vars:
+ category_name: "Integration Tests"
+ module_defaults:
+ lowlydba.sqlserver.agent_job_category:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ category: "{{ category_name }}"
+ tags: ["agent_job_category"]
+ block:
+ - name: Create job category
+ lowlydba.sqlserver.agent_job_category:
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.Name == "{{ category_name }}"
+ - result.data.ID != None
+ - result.data.CategoryType == "LocalJob"
+ - result.data.JobCount == 0
+
+ - name: Create existing job category
+ lowlydba.sqlserver.agent_job_category:
+ register: result
+ - assert:
+ that:
+ - result is not changed
+
+ - name: Remove job category in check mode
+ lowlydba.sqlserver.agent_job_category:
+ state: absent
+ register: result
+ check_mode: true
+ - assert:
+ that:
+ - result is changed
+
+ - name: Remove job category
+ lowlydba.sqlserver.agent_job_category:
+ state: absent
+ register: result
+ - assert:
+ that:
+ - result is changed
+
+ - name: Create new job category
+ lowlydba.sqlserver.agent_job_category:
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.Name == "{{ category_name }}"
+ - result.data.ID != None
+ - result.data.CategoryType == "LocalJob"
+ - result.data.JobCount == 0
+ - result is changed
+
+ always:
+ - name: Remove job category
+ lowlydba.sqlserver.agent_job_category:
+ state: absent
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_schedule/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_schedule/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_schedule/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_schedule/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_schedule/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_schedule/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_schedule/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_schedule/tasks/main.yml
new file mode 100644
index 00000000..5bd26654
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_schedule/tasks/main.yml
@@ -0,0 +1,169 @@
+---
+- name: Var block
+ vars:
+ forced_schedule_name: "Forced"
+ job_name: "Agent Job Schedule Integration Test"
+ start_date: "30200525"
+ start_time: "000000"
+ frequency_type: "Daily"
+ frequency_interval: "Everyday"
+ frequency_subday_type: "Hours"
+ frequency_subday_interval: 5
+ frequency_relative_interval: "First"
+ end_date: "30200525"
+ end_time: "000929"
+ start_date_result: "3020-05-25T00:00:00"
+ end_date_result: "3020-05-25T00:00:00"
+ module_defaults:
+ lowlydba.sqlserver.agent_job:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ job: "{{ job_name }}"
+ lowlydba.sqlserver.agent_job_schedule:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ start_date: "{{ start_date }}"
+ start_time: "{{ start_time }}"
+ end_date: "{{ end_date }}"
+ end_time: "{{ end_time }}"
+ frequency_type: "{{ frequency_type }}"
+ frequency_interval: "{{ frequency_interval }}"
+ frequency_subday_type: "{{ frequency_subday_type }}"
+ frequency_subday_interval: "{{ frequency_subday_interval }}"
+ frequency_relative_interval: "{{ frequency_relative_interval }}"
+ job: "{{ job_name }}"
+ tags: ["agent_job_schedule"]
+ block:
+ - name: Pre-create agent job
+ lowlydba.sqlserver.agent_job:
+ force: true
+ state: present
+ register: result
+ - assert:
+ that:
+ - result is changed
+
+ - name: Create job schedule with force
+ lowlydba.sqlserver.agent_job_schedule:
+ schedule: "{{ forced_schedule_name }}"
+ force: true
+ state: present
+ register: result
+ - assert:
+ that:
+ - result.data.ScheduleUid != None
+ - result.data.ActiveStartDate == "3020-05-25T00:00:00.0000000"
+ - result.data.ActiveEndDate == "3020-05-25T00:00:00.0000000"
+ - result.data.JobCount == 1
+ - result.data.IsEnabled is true
+ - result.data.ScheduleName == "{{ forced_schedule_name }}"
+ - result is changed
+
+ - name: Change job schedule & disable
+ lowlydba.sqlserver.agent_job_schedule:
+ schedule: "{{ forced_schedule_name }}"
+ start_date: "30210525"
+ end_date: "30210525"
+ enabled: false
+ state: present
+ register: result
+ - assert:
+ that:
+ - result.data.ScheduleUid != None
+ - result.data.ActiveStartDate == "3021-05-25T00:00:00.0000000"
+ - result.data.ActiveEndDate == "3021-05-25T00:00:00.0000000"
+ - result.data.JobCount == 1
+ - result.data.IsEnabled is false
+ - result.data.ScheduleName == "{{ forced_schedule_name }}"
+ - result is changed
+
+ - name: Enable job schedule
+ lowlydba.sqlserver.agent_job_schedule:
+ schedule: "{{ forced_schedule_name }}"
+ enabled: true
+ state: present
+ register: result
+ - assert:
+ that:
+ - result.data.ScheduleUid != None
+ - result.data.JobCount == 1
+ - result.data.IsEnabled is true
+ - result.data.ScheduleName == "{{ forced_schedule_name }}"
+ - result is changed
+
+ - name: No change
+ lowlydba.sqlserver.agent_job_schedule:
+ schedule: "{{ forced_schedule_name }}"
+ enabled: true
+ state: present
+ register: result
+ - assert:
+ that:
+ - result is not changed
+
+ - name: Remove job schedule
+ lowlydba.sqlserver.agent_job_schedule:
+ schedule: "{{ forced_schedule_name }}"
+ force: true
+ state: absent
+ register: result
+ - assert:
+ that:
+ - result is changed
+
+ - name: Remove non-existent job schedule
+ lowlydba.sqlserver.agent_job_schedule:
+ schedule: "{{ forced_schedule_name }}"
+ force: true
+ state: absent
+ register: result
+ - assert:
+ that:
+ - result is not changed
+
+ - name: Create job schedule in checkmode
+ lowlydba.sqlserver.agent_job_schedule:
+ schedule: "{{ forced_schedule_name }}"
+ start_date: "30210526"
+ end_date: "30210526"
+ enabled: false
+ state: present
+ register: result
+ check_mode: true
+ - assert:
+ that:
+ - result is changed
+
+ - name: Verify unchanged in checkmode
+ lowlydba.sqlserver.agent_job_schedule:
+ schedule: "{{ forced_schedule_name }}"
+ start_date: "30210526"
+ end_date: "30210526"
+ enabled: false
+ state: present
+ register: result
+ - assert:
+ that:
+ - result.data.ScheduleUid != None
+ - result.data.ActiveStartDate == "3021-05-26T00:00:00.0000000"
+ - result.data.ActiveEndDate == "3021-05-26T00:00:00.0000000"
+ - result.data.JobCount == 1
+ - result.data.IsEnabled is false
+ - result.data.ScheduleName == "{{ forced_schedule_name }}"
+ - result is changed
+
+ # Cleanup
+ always:
+ # Also cleans up associated schedules
+ - name: Remove test agent job
+ lowlydba.sqlserver.agent_job:
+ force: true
+ state: absent
+
+ - name: Remove job schedule
+ lowlydba.sqlserver.agent_job_schedule:
+ schedule: "{{ forced_schedule_name }}"
+ force: true
+ state: absent
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_step/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_step/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_step/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_step/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_step/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_step/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_step/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_step/tasks/main.yml
new file mode 100644
index 00000000..1cfbec42
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/agent_job_step/tasks/main.yml
@@ -0,0 +1,167 @@
+---
+- name: Var block
+ vars:
+ category_name: "Integration Tests"
+ job_name: "Ansible Integration Job Step Test"
+ job_step1: "One, you're like a dream come true"
+ job_step2: "Two, just wanna be with you"
+ job_step3: "Three, girl, it's plain to see"
+ module_defaults:
+ lowlydba.sqlserver.agent_job:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ lowlydba.sqlserver.agent_job_step:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ job: "{{ job_name }}"
+ tags: ["agent_job_step"]
+ block:
+ - name: Create agent job
+ lowlydba.sqlserver.agent_job:
+ job: "{{ job_name }}"
+ force: true
+ register: result
+ - assert:
+ that:
+ - result.data.Name == "{{ job_name }}"
+
+ - name: Create agent job step one
+ lowlydba.sqlserver.agent_job_step:
+ step_name: "{{ job_step1 }}"
+ step_id: 1
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.Name == "{{ job_step1 }}"
+ - result.data.ID == 1
+ - result.data.DatabaseName == "master"
+ - result.data.State == "Existing"
+ - result is changed
+
+ - name: Create agent job step two in checkmode
+ lowlydba.sqlserver.agent_job_step:
+ step_name: "{{ job_step2 }}"
+ step_id: 2
+ check_mode: true
+ register: result
+ - assert:
+ that:
+ - result is changed
+
+ - name: Verify create agent job step two in checkmode works
+ lowlydba.sqlserver.agent_job_step:
+ step_name: "{{ job_step2 }}"
+ step_id: 2
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.Name == "{{ job_step2 }}"
+ - result.data.ID == 2
+ - result.data.DatabaseName == "master"
+ - result.data.State == "Existing"
+ - result is changed
+
+ - name: Create agent job step three
+ lowlydba.sqlserver.agent_job_step:
+ step_name: "{{ job_step3 }}"
+ step_id: 3
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.Name == "{{ job_step3 }}"
+ - result.data.ID == 3
+ - result.data.DatabaseName == "master"
+ - result.data.State == "Existing"
+
+ - name: Create duplicate agent job step
+ lowlydba.sqlserver.agent_job_step:
+ step_name: "{{ job_step3 }}"
+ step_id: 4
+ register: result
+ failed_when: result is not failed
+ - assert:
+ that:
+ - result is not changed
+
+ - name: Change agent job step in checkmode
+ lowlydba.sqlserver.agent_job_step:
+ step_name: "{{ job_step3 }}"
+ step_id: 3
+ database: "model"
+ check_mode: true
+ register: result
+ - assert:
+ that:
+ - result is changed
+
+ - name: Verify change agent job step in checkmode works
+ lowlydba.sqlserver.agent_job_step:
+ step_name: "{{ job_step3 }}"
+ step_id: 3
+ database: "model"
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.Name == "{{ job_step3 }}"
+ - result.data.ID == 3
+ - result.data.DatabaseName == "model"
+ - result.data.State == "Existing"
+ - result is changed
+
+ - name: Verify no change works
+ lowlydba.sqlserver.agent_job_step:
+ step_name: "{{ job_step3 }}"
+ step_id: 3
+ database: "model"
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.Name == "{{ job_step3 }}"
+ - result.data.ID == 3
+ - result.data.DatabaseName == "model"
+ - result.data.State == "Existing"
+ - result is not changed
+
+ - name: Remove agent job step
+ lowlydba.sqlserver.agent_job_step:
+ step_name: "{{ job_step1 }}"
+ state: "absent"
+ register: result
+ - assert:
+ that:
+ - result is changed
+
+ - name: Remove agent job step in checkmode
+ lowlydba.sqlserver.agent_job_step:
+ step_id: 2
+ state: "absent"
+ check_mode: true
+ register: result
+ - assert:
+ that:
+ - result is changed
+
+ - name: Verify remove agent job step in checkmode works
+ lowlydba.sqlserver.agent_job_step:
+ step_id: 2
+ state: "absent"
+ register: result
+ - assert:
+ that:
+ - result is changed
+
+ always:
+ - name: Cleanup agent job
+ lowlydba.sqlserver.agent_job:
+ job: "{{ job_name }}"
+ state: "absent"
+ register: result
+ - assert:
+ that: result.data.Status == "Dropped"
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/backup/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/backup/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/backup/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/backup/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/backup/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/backup/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/backup/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/backup/tasks/main.yml
new file mode 100644
index 00000000..7678c29f
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/backup/tasks/main.yml
@@ -0,0 +1,25 @@
+---
+- name: Var block
+ vars:
+ database_name: "master"
+ tags: ["backup"]
+ block:
+ - name: Backup a database
+ lowlydba.sqlserver.backup:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ database: "{{ database_name }}"
+ block_size: "16kb"
+ register: result
+ - assert:
+ that:
+ - result.data.SqlInstance != None
+ - result.data.Database == "{{ database_name }}"
+ - result.data.Type == "Full"
+ - result.data.TotalSize != None
+ - result.data.DeviceType == "Disk"
+ - result.data.Start != None
+ - result.data.End != None
+ - result.data.Duration != None
+ - result is changed
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/credential/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/credential/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/credential/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/credential/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/credential/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/credential/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/credential/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/credential/tasks/main.yml
new file mode 100644
index 00000000..0a68c4bc
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/credential/tasks/main.yml
@@ -0,0 +1,85 @@
+---
+- name: Var block
+ vars:
+ identity: "TestIdentity"
+ name: "MrSmith"
+ password: "Password123!"
+ mapped_class_type: "None"
+ provider_name: ""
+ module_defaults:
+ lowlydba.sqlserver.credential:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ identity: "{{ identity }}"
+ tags: ["credential"]
+ block:
+ - name: Create a new credential
+ lowlydba.sqlserver.credential:
+ name: "{{ name }}"
+ password: "{{ password }}"
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.Identity == "{{ identity }}"
+ - result.data.Name == "{{ name }}"
+ - result is changed
+
+ - name: Try to add the same credential
+ lowlydba.sqlserver.credential:
+ name: "{{ name }}"
+ password: "{{ password }}"
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.Identity == "{{ identity }}"
+ - result.data.Name == "{{ name }}"
+ - result is not changed
+
+ - name: Replace an existing credential
+ lowlydba.sqlserver.credential:
+ force: true
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.Identity == "{{ identity }}"
+ - result.data.Name == "{{ identity }}"
+ - result is changed
+
+ - name: Drop credential
+ lowlydba.sqlserver.credential:
+ state: "absent"
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result is changed
+
+ - name: Create credential in check mode
+ lowlydba.sqlserver.credential:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ identity: "{{ identity }}"
+ register: result
+ check_mode: true
+ - assert:
+ that:
+ - result is changed
+
+ always:
+ - name: Drop credential
+ lowlydba.sqlserver.credential:
+ state: "absent"
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/database/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/database/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/database/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/database/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/database/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/database/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/database/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/database/tasks/main.yml
new file mode 100644
index 00000000..93c9368c
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/database/tasks/main.yml
@@ -0,0 +1,92 @@
+---
+- name: Var block
+ vars:
+ database_name: "sqlserver_integration_db"
+ maxdop: 1
+ secondary_maxdop: 4
+ owner_name: sa
+ recovery_model: Simple
+ compatibility_low: Version140
+ compatibility_high: Version150
+ module_defaults:
+ lowlydba.sqlserver.database:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ database: "{{ database_name }}"
+ tags: ["database"]
+ block:
+ - name: Ensure a database exists
+ lowlydba.sqlserver.database:
+ maxdop: "{{ maxdop }}"
+ secondary_maxdop: "{{ secondary_maxdop }}"
+ rcsi: true
+ recovery_model: "{{ recovery_model }}"
+ compatibility: "{{ compatibility_low }}"
+ owner: "{{ sqlserver_username }}"
+ register: result
+ - assert:
+ that:
+ - result.data.Name == "{{ database_name }}"
+ - result.data.RCSI is true
+ - result.data.Owner == "{{ sqlserver_username }}"
+ - result.data.RecoveryModel == "{{ recovery_model }}"
+ - result.data.MaxDop == {{ maxdop }}
+ - result.data.Compatibility == "{{ compatibility_low }}"
+
+ - name: Change a database
+ lowlydba.sqlserver.database:
+ compatibility: "{{ compatibility_high }}"
+ owner: "{{ owner_name }}"
+ secondary_maxdop: "{{ secondary_maxdop }}"
+ register: result
+ - assert:
+ that:
+ - result is changed
+ - result.data.Compatibility =="{{ compatibility_high }}"
+ - result.data.Owner == "{{ owner_name }}"
+ - result.data.SecondaryMaxDop == {{ secondary_maxdop }}
+
+ - name: Change a database in checkmode
+ lowlydba.sqlserver.database:
+ database: "{{ database_name }}"
+ rcsi: false
+ register: result
+ check_mode: true
+ - assert:
+ that:
+ - result is changed
+
+ - name: Verify database unchanged from checkmode
+ lowlydba.sqlserver.database:
+ database: "{{ database_name }}"
+ rcsi: false
+ register: result
+ - assert:
+ that:
+ - result is changed
+ - result.data.RCSI is false
+
+ - name: Drop a database
+ lowlydba.sqlserver.database:
+ database: "{{ database_name }}"
+ state: absent
+ register: result
+ - assert:
+ that:
+ - result is changed
+
+ - name: Create a database
+ lowlydba.sqlserver.database:
+ database: "{{ database_name }}"
+ register: result
+ - assert:
+ that:
+ - result is changed
+ - result.data.Name == "{{ database_name }}"
+
+ always:
+ - name: Drop a database
+ lowlydba.sqlserver.database:
+ database: "{{ database_name }}"
+ state: absent
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/dba_multitool/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/dba_multitool/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/dba_multitool/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/dba_multitool/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/dba_multitool/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/dba_multitool/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/dba_multitool/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/dba_multitool/tasks/main.yml
new file mode 100644
index 00000000..b548ee7a
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/dba_multitool/tasks/main.yml
@@ -0,0 +1,37 @@
+---
+- name: Var block
+ vars:
+ target_database: "master"
+ branch: "development"
+ module_defaults:
+ lowlydba.sqlserver.dba_multitool:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ database: "{{ target_database }}"
+ branch: "{{ branch }}"
+ tags: ["dba_multitool"]
+ block:
+ - name: Install dba_multitool
+ lowlydba.sqlserver.dba_multitool:
+ register: result
+ - assert:
+ that:
+ - result.data.SqlInstance != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.Database == target_database
+ - result.data.Status in ('Installed', 'Updated')
+ - result is changed
+
+ - name: Update dba_multitool
+ lowlydba.sqlserver.dba_multitool:
+ register: result
+ - assert:
+ that:
+ - result.data.SqlInstance != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.Database == target_database
+ - result.data.Status == 'Updated'
+ - result is changed
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/first_responder_kit/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/first_responder_kit/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/first_responder_kit/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/first_responder_kit/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/first_responder_kit/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/first_responder_kit/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/first_responder_kit/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/first_responder_kit/tasks/main.yml
new file mode 100644
index 00000000..e5dd01a8
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/first_responder_kit/tasks/main.yml
@@ -0,0 +1,39 @@
+---
+- name: Var block
+ vars:
+ target_database: "master"
+ branch: "main"
+ module_defaults:
+ lowlydba.sqlserver.first_responder_kit:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ database: "{{ target_database }}"
+ branch: "{{ branch }}"
+ only_script: "Install-All-Scripts.sql"
+ tags: ["first_responder_kit"]
+ block:
+ - name: Install first_responder_kit
+ lowlydba.sqlserver.first_responder_kit:
+ register: result
+ - assert:
+ that:
+ - result.data.SqlInstance != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.Database == target_database
+ - result.data.Status in ('Installed', 'Updated')
+ - result is changed
+
+ - name: Uninstall first_responder_kit
+ lowlydba.sqlserver.first_responder_kit:
+ only_script: "Uninstall.sql"
+ register: result
+ - assert:
+ that:
+ - result.data.SqlInstance != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.Database == target_database
+ - result.data.Status in ('Installed', 'Updated')
+ - result is changed
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/install_script/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/install_script/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/install_script/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/install_script/files/1-select-choice.sql b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/install_script/files/1-select-choice.sql
new file mode 100644
index 00000000..54a481ac
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/install_script/files/1-select-choice.sql
@@ -0,0 +1 @@
+SELECT 'choice';
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/install_script/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/install_script/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/install_script/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/install_script/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/install_script/tasks/main.yml
new file mode 100644
index 00000000..f363c031
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/install_script/tasks/main.yml
@@ -0,0 +1,61 @@
+---
+- name: Var block
+ vars:
+ path: "{{ role_path }}/files/"
+ database_name: "lowlydba-migration-test"
+ module_defaults:
+ lowlydba.sqlserver.install_script:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ create_database: true
+ no_recurse: true
+ database: "{{ database_name }}"
+ path: "{{ path }}"
+ schema_version_table: "SchemaVersion"
+ output_file: "output.txt"
+ tags: ["sqlserver.install_script"]
+
+ block:
+ - name: Install a script in no log mode
+ lowlydba.sqlserver.install_script:
+ no_log_version: true
+ register: result
+ - assert:
+ that:
+ - result.data.Database == "{{ database_name }}"
+ - result.data.Successful == true
+ - result is changed
+
+ - name: Install a script in checkmode
+ lowlydba.sqlserver.install_script:
+ register: result
+ check_mode: true
+ - assert:
+ that:
+ - result is changed
+
+ - name: Install a script
+ lowlydba.sqlserver.install_script:
+ register: result
+ - assert:
+ that:
+ - result.data.Database == "{{ database_name }}"
+ - result.data.Successful == true
+ - result is changed
+
+ - name: Install same script
+ lowlydba.sqlserver.install_script:
+ register: result
+ - assert:
+ that:
+ - result is not changed
+
+ always:
+ - name: Cleanup database
+ lowlydba.sqlserver.database:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ database: "{{ database_name }}"
+ state: absent
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/instance_info/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/instance_info/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/instance_info/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/instance_info/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/instance_info/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/instance_info/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/instance_info/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/instance_info/tasks/main.yml
new file mode 100644
index 00000000..f7ac97a6
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/instance_info/tasks/main.yml
@@ -0,0 +1,26 @@
+---
+- name: Var block
+ module_defaults:
+ lowlydba.sqlserver.instance_info:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ tags: ["instance_info"]
+ block:
+ - name: Get instance info
+ lowlydba.sqlserver.instance_info:
+ register: result
+ - assert:
+ that:
+ - result.data.SqlInstance != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.BuildNumber != None
+ - result.data.Language != None
+ - result.data.VersionMajor != None
+ - result.data.VersionMinor != None
+ - result.data.VersionString != None
+ - result.data.Collation != None
+ - result.data.ProductLevel != None
+ - result.data.IsClustered != None
+ - result.data.LoginMode != None
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/login/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/login/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/login/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/login/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/login/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/login/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/login/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/login/tasks/main.yml
new file mode 100644
index 00000000..da938fc5
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/login/tasks/main.yml
@@ -0,0 +1,97 @@
+---
+- name: Var block
+ vars:
+ login_name: "PhillipJFry"
+ plain_password: "P0pS3cret!23$%"
+ password_expiration_enabled: false
+ password_policy_enforced: false
+ password_must_change: false
+ enabled: false
+ default_database: "master"
+ language: "us_english"
+ module_defaults:
+ lowlydba.sqlserver.login:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ default_database: "{{ default_database }}"
+ login: "{{ login_name }}"
+ password: "{{ plain_password }}"
+ password_expiration_enabled: "{{ password_expiration_enabled }}"
+ password_must_change: "{{ password_must_change }}"
+ enabled: "{{ enabled }}"
+ language: "{{ language }}"
+ state: present
+ tags: ["sqlserver.login"]
+ block:
+ - name: Create login
+ lowlydba.sqlserver.login:
+ password_policy_enforced: "{{ password_policy_enforced }}"
+ register: result
+ - assert:
+ that:
+ - result.data != None
+
+ - name: Modify login
+ lowlydba.sqlserver.login:
+ default_database: "model"
+ enabled: true
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.IsDisabled is false
+ - result.data.Name == "{{ login_name }}"
+ - result.data.DefaultDatabase == "model"
+
+ - name: Drop login
+ lowlydba.sqlserver.login:
+ state: "absent"
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.Login == "{{ login_name }}"
+ - result.data.Status == "Dropped"
+ - result is changed
+
+ - name: Create login in checkmode
+ lowlydba.sqlserver.login:
+ password_policy_enforced: true
+ password_expiration_enabled: true
+ password_must_change: true
+ enabled: false
+ register: result
+ check_mode: true
+ - assert:
+ that:
+ - result is changed
+
+ - name: Verify checkmode works
+ lowlydba.sqlserver.login:
+ enabled: false
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.MustChangePassword is false
+ - result.data.IsDisabled is true
+ - result.data.IsLocked is false
+ - result.data.Name == "{{ login_name }}"
+ - result.data.DefaultDatabase == "{{ default_database }}"
+ - result.data.Language == "{{ language }}"
+ - result is changed
+
+ always:
+ - name: Drop login
+ lowlydba.sqlserver.login:
+ state: "absent"
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/maintenance_solution/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/maintenance_solution/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/maintenance_solution/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/maintenance_solution/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/maintenance_solution/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/maintenance_solution/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/maintenance_solution/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/maintenance_solution/tasks/main.yml
new file mode 100644
index 00000000..9dd7b668
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/maintenance_solution/tasks/main.yml
@@ -0,0 +1,29 @@
+---
+- name: Var block
+ module_defaults:
+ lowlydba.sqlserver.maintenance_solution:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ backup_location: "C:\\backup"
+ cleanup_time: 24
+ output_file_dir: "C:\\logs"
+ tags: ["maintenance_solution"]
+ block:
+ - name: Install Maintenance Solution
+ lowlydba.sqlserver.maintenance_solution:
+ database: master
+ replace_existing: true
+ register: result
+ - assert:
+ that:
+ - result.data.Results == "Success"
+ - result is changed
+
+ - name: Install Maintenance Solution if not already present
+ lowlydba.sqlserver.maintenance_solution:
+ database: master
+ register: result
+ - assert:
+ that:
+ - result is not changed
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/memory/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/memory/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/memory/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/memory/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/memory/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/memory/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/memory/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/memory/tasks/main.yml
new file mode 100644
index 00000000..eb2f54fa
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/memory/tasks/main.yml
@@ -0,0 +1,71 @@
+---
+- name: Var block
+ vars:
+ max_memory_over_9000: 99999
+ module_defaults:
+ lowlydba.sqlserver.memory:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ tags: ["memory"]
+ block:
+ - name: Set max memory
+ lowlydba.sqlserver.memory:
+ max: "{{ max_memory_over_9000 }}"
+ register: result
+ - assert:
+ that:
+ - result.data.Total != None
+ - result.data.PreviousMaxValue is defined
+ - result.data.PreviousMaxValue != None
+ - result.data.MaxValue == {{ max_memory_over_9000 }}
+
+ - name: Don't change max memory
+ lowlydba.sqlserver.memory:
+ max: "{{ max_memory_over_9000 }}"
+ register: result
+ - assert:
+ that:
+ - result is not changed
+
+ - name: Dynamically set max memory
+ lowlydba.sqlserver.memory:
+ max: 0
+ register: result
+ - assert:
+ that:
+ - result.data.PreviousMaxValue == {{ max_memory_over_9000 }}
+ - result.data.MaxValue != {{ max_memory_over_9000 }}
+ - result.data.Total > result.data.MaxValue
+
+ - name: No change with dynamic memory
+ lowlydba.sqlserver.memory:
+ max: 0
+ register: result
+ - assert:
+ that:
+ - result is not changed
+
+ - name: Set max memory in checkmode
+ lowlydba.sqlserver.memory:
+ max: "{{ max_memory_over_9000 }}"
+ check_mode: true
+ register: result
+ - assert:
+ that:
+ - result is changed
+
+ - name: Verify unchanged checkmode max memory
+ lowlydba.sqlserver.memory:
+ max: "{{ max_memory_over_9000 }}"
+ register: result
+ - assert:
+ that:
+ - result.data.PreviousMaxValue != {{ max_memory_over_9000 }}
+ - result.data.MaxValue == {{ max_memory_over_9000 }}
+ - result is changed
+
+ always:
+ - name: Dynamically set max memory
+ lowlydba.sqlserver.memory:
+ max: 0
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/nonquery/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/nonquery/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/nonquery/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/nonquery/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/nonquery/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/nonquery/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/nonquery/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/nonquery/tasks/main.yml
new file mode 100644
index 00000000..9b6d2235
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/nonquery/tasks/main.yml
@@ -0,0 +1,27 @@
+---
+- name: Var block
+ module_defaults:
+ lowlydba.sqlserver.nonquery:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ tags: ["query"]
+ block:
+ - name: Execute a nonquery
+ lowlydba.sqlserver.nonquery:
+ nonquery: "SELECT 1"
+ database: "master"
+ register: result
+ - assert:
+ that:
+ - result is changed
+
+ - name: Execute a nonquery in checkmode
+ lowlydba.sqlserver.nonquery:
+ nonquery: "SELECT 1"
+ database: "master"
+ check_mode: true
+ register: result
+ - assert:
+ that:
+ - result is changed
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/resource_governor/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/resource_governor/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/resource_governor/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/resource_governor/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/resource_governor/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/resource_governor/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/resource_governor/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/resource_governor/tasks/main.yml
new file mode 100644
index 00000000..328f9cd0
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/resource_governor/tasks/main.yml
@@ -0,0 +1,36 @@
+---
+- name: Var block
+ module_defaults:
+ lowlydba.sqlserver.resource_governor:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ tags: ["resource_governor"]
+ block:
+ - name: Enable resource_governor
+ lowlydba.sqlserver.resource_governor:
+ enabled: true
+ register: result
+
+ - name: Disable resource_governor
+ lowlydba.sqlserver.resource_governor:
+ enabled: false
+ register: result
+ - assert:
+ that:
+ - result.data.Enabled is false
+ - result.data.ComputerName != None
+ - result.data.SqlInstance != None
+ - "'default' in result.data.ResourcePools"
+ - result is changed
+
+ - name: Enable resource_governor
+ lowlydba.sqlserver.resource_governor:
+ enabled: true
+ register: result
+ - assert:
+ that:
+ - result.data.Enabled is true
+ - result.data.ComputerName != None
+ - result.data.SqlInstance != None
+ - "'default' in result.data.ResourcePools"
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_resource_pool/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_resource_pool/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_resource_pool/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_resource_pool/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_resource_pool/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_resource_pool/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_resource_pool/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_resource_pool/tasks/main.yml
new file mode 100644
index 00000000..d6ab8d93
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_resource_pool/tasks/main.yml
@@ -0,0 +1,76 @@
+---
+- name: Var block
+ vars:
+ resource_pool: "rpTheWaterIsFine"
+ type: "Internal"
+ max_cpu_perc: 99
+ min_cpu_perc: 90
+ max_iops_per_vol: 10000
+ min_iops_per_vol: 1
+ max_mem_perc: 99
+ min_mem_perc: 42
+ module_defaults:
+ lowlydba.sqlserver.rg_resource_pool:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ resource_pool: "{{ resource_pool }}"
+ type: "{{ type }}"
+ max_cpu_perc: "{{ max_cpu_perc }}"
+ min_cpu_perc: "{{ min_cpu_perc }}"
+ max_iops_per_vol: "{{ max_iops_per_vol }}"
+ min_iops_per_vol: "{{ min_iops_per_vol }}"
+ max_mem_perc: "{{ max_mem_perc }}"
+ min_mem_perc: "{{ min_mem_perc }}"
+ tags: ["rg_resource_pool"]
+ block:
+ - name: Create resource pool
+ lowlydba.sqlserver.rg_resource_pool:
+ state: "present"
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.ComputerName != None
+ - result.data.SqlInstance != None
+ - result.data.MaximumCpuPercentage == {{ max_cpu_perc }}
+ - result.data.MinimumCpuPercentage == {{ min_cpu_perc }}
+ - result.data.MaximumIopsPerVolume == {{ max_iops_per_vol }}
+ - result.data.MinimumIopsPerVolume == {{ min_iops_per_vol }}
+ - result.data.MaximumMemoryPercentage == {{ max_mem_perc }}
+ - result.data.MinimumMemoryPercentage == {{ min_mem_perc }}
+ - result.data.Name == "{{ resource_pool }}"
+
+ - name: Modify resource pool
+ lowlydba.sqlserver.rg_resource_pool:
+ min_cpu_perc: 1
+ state: "present"
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.ComputerName != None
+ - result.data.SqlInstance != None
+ - result.data.MinimumCpuPercentage == 1
+
+ - name: Drop resource pool in checkmode
+ lowlydba.sqlserver.rg_resource_pool:
+ state: "absent"
+ register: result
+ check_mode: true
+ - assert:
+ that:
+ - result is changed
+
+ - name: Drop resource pool
+ lowlydba.sqlserver.rg_resource_pool:
+ state: "absent"
+ register: result
+ - assert:
+ that:
+ - result is changed
+
+ always:
+ - name: Drop resource pool
+ lowlydba.sqlserver.rg_resource_pool:
+ state: "absent"
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_workload_group/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_workload_group/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_workload_group/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_workload_group/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_workload_group/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_workload_group/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_workload_group/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_workload_group/tasks/main.yml
new file mode 100644
index 00000000..449663e1
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/rg_workload_group/tasks/main.yml
@@ -0,0 +1,102 @@
+---
+- name: Var block
+ vars:
+ workload_group: "rgReports"
+ resource_pool: "rpReports"
+ resource_pool_type: "Internal"
+ max_dop: 2
+ request_max_cpu_time: 10
+ request_max_mem_grant_perc: 50
+ request_mem_grant_timeout_sec: 420
+ importance: "Medium"
+ module_defaults:
+ lowlydba.sqlserver.rg_workload_group:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ workload_group: "{{ workload_group }}"
+ resource_pool: "{{ resource_pool }}"
+ resource_pool_type: "{{ resource_pool_type }}"
+ request_max_cpu_time: "{{ request_max_cpu_time }}"
+ request_max_mem_grant_perc: "{{ request_max_mem_grant_perc }}"
+ request_mem_grant_timeout_sec: "{{ request_mem_grant_timeout_sec }}"
+ importance: "{{ importance }}"
+ max_dop: "{{ max_dop }}"
+ lowlydba.sqlserver.rg_resource_pool:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ resource_pool: "{{ resource_pool }}"
+ type: "{{ resource_pool_type }}"
+ tags: ["rg_workload_group"]
+ block:
+ - name: Create resource pool
+ lowlydba.sqlserver.rg_resource_pool:
+ state: "present"
+
+ - name: Create workload group
+ lowlydba.sqlserver.rg_workload_group:
+ state: "present"
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.ComputerName != None
+ - result.data.SqlInstance != None
+ - result.data.Importance == "{{ importance }}"
+ - result.data.Name == "{{ workload_group }}"
+ - result.data.MaximumDegreeOfParallelism == {{ max_dop }}
+ - result.data.RequestMaximumCpuTimeInSeconds == {{ request_max_cpu_time }}
+ - result.data.RequestMemoryGrantTimeoutInSeconds == {{ request_mem_grant_timeout_sec }}
+ - result.data.GroupMaximumRequests == 0
+ - result.data.RequestMaximumMemoryGrantPercentage == {{ request_max_mem_grant_perc }}
+
+ - name: Modify workload group
+ lowlydba.sqlserver.rg_workload_group:
+ group_max_requests: 4
+ state: "present"
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.ComputerName != None
+ - result.data.SqlInstance != None
+ - result.data.MaximumDegreeOfParallelism == {{ max_dop }}
+ - result.data.Importance == "{{ importance }}"
+ - result.data.Name == "{{ workload_group }}"
+ - result.data.RequestMaximumCpuTimeInSeconds == {{ request_max_cpu_time }}
+ - result.data.RequestMemoryGrantTimeoutInSeconds == {{ request_mem_grant_timeout_sec }}
+ - result.data.GroupMaximumRequests == 4
+ - result.data.RequestMaximumMemoryGrantPercentage == {{ request_max_mem_grant_perc }}
+
+ - name: Drop workload group in checkmode
+ lowlydba.sqlserver.rg_workload_group:
+ state: "absent"
+ register: result
+ check_mode: true
+ - assert:
+ that:
+ - result is changed
+
+ - name: Drop workload group
+ lowlydba.sqlserver.rg_workload_group:
+ state: "absent"
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.ComputerName != None
+ - result.data.SqlInstance != None
+ - result.data.Name == "{{ workload_group }}"
+ - result.data.Status == "Dropped"
+ - result is changed
+
+ always:
+ - name: Drop workload group
+ lowlydba.sqlserver.rg_workload_group:
+ state: "absent"
+
+ - name: Drop resource pool
+ lowlydba.sqlserver.rg_resource_pool:
+ resource_pool: "{{ resource_pool }}"
+ state: "absent"
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sa/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sa/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sa/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sa/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sa/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sa/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sa/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sa/tasks/main.yml
new file mode 100644
index 00000000..89a3c135
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sa/tasks/main.yml
@@ -0,0 +1,66 @@
+---
+- name: Var block
+ vars:
+ password_expiration_enabled: false
+ password_policy_enforced: false
+ password_must_change: false
+ enabled: true
+ module_defaults:
+ lowlydba.sqlserver.sa:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ tags: ["sqlserver.login"]
+ block:
+ - name: Configure sa
+ lowlydba.sqlserver.sa:
+ password_expiration_enabled: "{{ password_expiration_enabled }}"
+ password_policy_enforced: "{{ password_policy_enforced }}"
+ password_must_change: "{{ password_must_change }}"
+ enabled: "{{ enabled }}"
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.IsDisabled is false
+ - result.data.IsLocked is false
+ - result.data.Name == "sa"
+ - result.data.MustChangePassword is false
+
+ - name: Disable in checkmode
+ lowlydba.sqlserver.sa:
+ new_name: UpDawg
+ password: "WhatsUpDawg?"
+ enabled: false
+ password_expiration_enabled: true
+ password_must_change: true
+ password_policy_enforced: true
+ check_mode: true
+ register: result
+ - assert:
+ that:
+ - result is changed
+
+ - name: Verify checkmode works
+ lowlydba.sqlserver.sa:
+ enabled: "{{ enabled }}"
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.MustChangePassword is false
+ - result.data.IsDisabled is false
+ - result.data.IsLocked is false
+ - result.data.Name == "sa"
+ - result is not changed
+ # Cleanup
+ always:
+ - name: Enable sa
+ lowlydba.sqlserver.sa:
+ enabled: true
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver/defaults/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver/defaults/main.yml
new file mode 100644
index 00000000..5e3e5017
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+sqlserver_windows: false
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver/tasks/main.yml
new file mode 100644
index 00000000..28c3c1cd
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver/tasks/main.yml
@@ -0,0 +1,9 @@
+---
+- name: Install Powershell modules
+ when: not sqlserver_windows
+ ansible.builtin.command: >
+ pwsh -Command "{{ item }}"
+ no_log: "{{ ansible_verbosity | int < 3 }}"
+ loop:
+ - "{{ dbatools_install_cmd }}"
+ - "{{ dbops_install_cmd }}"
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver/vars/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver/vars/main.yml
new file mode 100644
index 00000000..3f850330
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver/vars/main.yml
@@ -0,0 +1,12 @@
+---
+dbatools_min_version: 1.1.112
+dbatools_install_cmd: >
+ if (-not(Get-Module -FullyQualifiedName @{ModuleName='dbatools';ModuleVersion='{{ dbatools_min_version }}'} -ListAvailable)) {
+ Install-Module dbatools -MinimumVersion {{ dbatools_min_version }} -Force
+ }
+
+dbops_min_version: 0.8.0
+dbops_install_cmd: >
+ if (-not(Get-Module -FullyQualifiedName @{ModuleName='dbops';ModuleVersion='{{ dbops_min_version }}'} -ListAvailable)) {
+ Install-Module dbops -MinimumVersion {{ dbops_min_version }} -Force
+ }
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/README.md b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/README.md
new file mode 100644
index 00000000..b55cd1cd
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/README.md
@@ -0,0 +1,9 @@
+# What is this?
+
+This collection contains a connection plugin and a shell plugin, with the goal of running Ansible PowerShell modules (traditionally only able to be run on remote Windows hosts), directly on the Ansible controller via `pwsh`, without modifications to the modules.
+
+## Supportability
+
+This is extremely experimental, and relies at least in part, on some hackery. Use with caution.
+
+The goal of not modifying modules to run this way _should_ support the notion that if this hackery fails, the module could instead be run against a remote Windows host as a sort of proxy, as long as the module already does its work against a remote system (like modules intended to manage SQL servers).
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/connection_plugins/local_pwsh.py b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/connection_plugins/local_pwsh.py
new file mode 100644
index 00000000..18ec57fd
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/connection_plugins/local_pwsh.py
@@ -0,0 +1,197 @@
+# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
+# (c) 2015, 2017 Toshio Kuratomi <tkuratomi@ansible.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = '''
+ name: local_pwsh
+ short_description: execute on controller via pwsh
+ description:
+ - This connection plugin allows ansible to execute tasks on the Ansible 'controller' instead of on a remote host.
+ author: ansible (@core)
+ version_added: historical
+ extends_documentation_fragment:
+ - connection_pipelining
+ notes:
+ - The remote user is ignored, the user with which the ansible CLI was executed is used instead.
+'''
+
+import os
+import sys
+import pty
+import shutil
+import subprocess
+import fcntl
+import getpass
+
+import ansible.constants as C
+from ansible.errors import AnsibleError, AnsibleFileNotFound
+from ansible.module_utils.compat import selectors
+from ansible.module_utils.six import text_type, binary_type
+from ansible.module_utils._text import to_bytes, to_native, to_text
+from ansible.plugins.connection import ConnectionBase
+from ansible.utils.display import Display
+from ansible.utils.path import unfrackpath
+
+display = Display()
+
+
+class Connection(ConnectionBase):
+ ''' Local based connections '''
+
+ transport = 'local'
+ has_pipelining = True
+ module_implementation_preferences = ('.ps1', '')
+
+ def __init__(self, *args, **kwargs):
+
+ super(Connection, self).__init__(*args, **kwargs)
+ self.cwd = None
+ self.default_user = getpass.getuser()
+
+ def _connect(self):
+ ''' connect to the local host; nothing to do here '''
+
+ # Because we haven't made any remote connection we're running as
+ # the local user, rather than as whatever is configured in remote_user.
+ self._play_context.remote_user = self.default_user
+
+ if not self._connected:
+ display.vvv(u"ESTABLISH LOCAL CONNECTION FOR USER: {0}".format(self._play_context.remote_user), host=self._play_context.remote_addr)
+ self._connected = True
+ return self
+
+ def exec_command(self, cmd, in_data=None, sudoable=True):
+ ''' run a command on the local host '''
+
+ super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
+
+ display.debug("in local_pwsh.exec_command()")
+
+ # mac (darwin) has different pwsh install location than linux
+ if sys.platform.startswith('darwin'):
+ executable = '/usr/local/bin/pwsh'
+ else:
+ executable = '/usr/bin/pwsh'
+ # executable = C.DEFAULT_EXECUTABLE.split()[0] if C.DEFAULT_EXECUTABLE else None
+
+ if not os.path.exists(to_bytes(executable, errors='surrogate_or_strict')):
+ raise AnsibleError("failed to find the executable specified %s."
+ " Please verify if the executable exists and re-try." % executable)
+
+ display.vvv(u"EXEC {0}".format(to_text(cmd)), host=self._play_context.remote_addr)
+ display.debug("opening command with Popen()")
+
+ if isinstance(cmd, (text_type, binary_type)):
+ cmd = to_bytes(cmd)
+ else:
+ cmd = map(to_bytes, cmd)
+
+ master = None
+ stdin = subprocess.PIPE
+ if sudoable and self.become and self.become.expect_prompt() and not self.get_option('pipelining'):
+ # Create a pty if sudoable for privlege escalation that needs it.
+ # Falls back to using a standard pipe if this fails, which may
+ # cause the command to fail in certain situations where we are escalating
+ # privileges or the command otherwise needs a pty.
+ try:
+ master, stdin = pty.openpty()
+ except (IOError, OSError) as e:
+ display.debug("Unable to open pty: %s" % to_native(e))
+
+ p = subprocess.Popen(
+ cmd,
+ shell=isinstance(cmd, (text_type, binary_type)),
+ executable=executable,
+ cwd=self.cwd,
+ stdin=stdin,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+
+ # if we created a master, we can close the other half of the pty now, otherwise master is stdin
+ if master is not None:
+ os.close(stdin)
+
+ display.debug("done running command with Popen()")
+
+ if self.become and self.become.expect_prompt() and sudoable:
+ fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) | os.O_NONBLOCK)
+ fcntl.fcntl(p.stderr, fcntl.F_SETFL, fcntl.fcntl(p.stderr, fcntl.F_GETFL) | os.O_NONBLOCK)
+ selector = selectors.DefaultSelector()
+ selector.register(p.stdout, selectors.EVENT_READ)
+ selector.register(p.stderr, selectors.EVENT_READ)
+
+ become_output = b''
+ try:
+ while not self.become.check_success(become_output) and not self.become.check_password_prompt(become_output):
+ events = selector.select(self._play_context.timeout)
+ if not events:
+ stdout, stderr = p.communicate()
+ raise AnsibleError('timeout waiting for privilege escalation password prompt:\n' + to_native(become_output))
+
+ for key, event in events:
+ if key.fileobj == p.stdout:
+ chunk = p.stdout.read()
+ elif key.fileobj == p.stderr:
+ chunk = p.stderr.read()
+
+ if not chunk:
+ stdout, stderr = p.communicate()
+ raise AnsibleError('privilege output closed while waiting for password prompt:\n' + to_native(become_output))
+ become_output += chunk
+ finally:
+ selector.close()
+
+ if not self.become.check_success(become_output):
+ become_pass = self.become.get_option('become_pass', playcontext=self._play_context)
+ if master is None:
+ p.stdin.write(to_bytes(become_pass, errors='surrogate_or_strict') + b'\n')
+ else:
+ os.write(master, to_bytes(become_pass, errors='surrogate_or_strict') + b'\n')
+
+ fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) & ~os.O_NONBLOCK)
+ fcntl.fcntl(p.stderr, fcntl.F_SETFL, fcntl.fcntl(p.stderr, fcntl.F_GETFL) & ~os.O_NONBLOCK)
+
+ display.debug("getting output with communicate()")
+ stdout, stderr = p.communicate(in_data)
+ display.debug("done communicating")
+
+ # finally, close the other half of the pty, if it was created
+ if master:
+ os.close(master)
+
+ display.debug("done with local.exec_command()")
+ return (p.returncode, stdout, stderr)
+
+ def put_file(self, in_path, out_path):
+ ''' transfer a file from local to local '''
+
+ super(Connection, self).put_file(in_path, out_path)
+
+ in_path = unfrackpath(in_path, basedir=self.cwd)
+ out_path = unfrackpath(out_path, basedir=self.cwd)
+
+ display.vvv(u"PUT {0} TO {1}".format(in_path, out_path), host=self._play_context.remote_addr)
+ if not os.path.exists(to_bytes(in_path, errors='surrogate_or_strict')):
+ raise AnsibleFileNotFound("file or module does not exist: {0}".format(to_native(in_path)))
+ try:
+ shutil.copyfile(to_bytes(in_path, errors='surrogate_or_strict'), to_bytes(out_path, errors='surrogate_or_strict'))
+ except shutil.Error:
+ raise AnsibleError("failed to copy: {0} and {1} are the same".format(to_native(in_path), to_native(out_path)))
+ except IOError as e:
+ raise AnsibleError("failed to transfer file to {0}: {1}".format(to_native(out_path), to_native(e)))
+
+ def fetch_file(self, in_path, out_path):
+ ''' fetch a file from local to local -- for compatibility '''
+
+ super(Connection, self).fetch_file(in_path, out_path)
+
+ display.vvv(u"FETCH {0} TO {1}".format(in_path, out_path), host=self._play_context.remote_addr)
+ self.put_file(in_path, out_path)
+
+ def close(self):
+ ''' terminate the connection; nothing to do here '''
+ self._connected = False
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/module_utils/Ansible.Basic.cs b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/module_utils/Ansible.Basic.cs
new file mode 100644
index 00000000..484f7575
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/module_utils/Ansible.Basic.cs
@@ -0,0 +1,1481 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Management.Automation;
+using System.Management.Automation.Runspaces;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Security.AccessControl;
+using System.Security.Principal;
+#if CORECLR
+using Newtonsoft.Json;
+#else
+using System.Web.Script.Serialization;
+#endif
+
+// System.Diagnostics.EventLog.dll reference different versioned dlls that are
+// loaded in PSCore, ignore CS1702 so the code will ignore this warning
+//NoWarn -Name CS1702 -CLR Core
+
+//AssemblyReference -Type Newtonsoft.Json.JsonConvert -CLR Core
+//AssemblyReference -Type System.Diagnostics.EventLog -CLR Core
+//AssemblyReference -Type System.Security.AccessControl.NativeObjectSecurity -CLR Core
+//AssemblyReference -Type System.Security.AccessControl.DirectorySecurity -CLR Core
+//AssemblyReference -Type System.Security.Principal.IdentityReference -CLR Core
+
+//AssemblyReference -Name System.Web.Extensions.dll -CLR Framework
+
+namespace Ansible.Basic
+{
+ public class AnsibleModule
+ {
+ public delegate void ExitHandler(int rc);
+ public static ExitHandler Exit = new ExitHandler(ExitModule);
+
+ public delegate void WriteLineHandler(string line);
+ public static WriteLineHandler WriteLine = new WriteLineHandler(WriteLineModule);
+
+ public static bool _DebugArgSpec = false;
+
+ private static List<string> BOOLEANS_TRUE = new List<string>() { "y", "yes", "on", "1", "true", "t", "1.0" };
+ private static List<string> BOOLEANS_FALSE = new List<string>() { "n", "no", "off", "0", "false", "f", "0.0" };
+
+ private string remoteTmp = Path.GetTempPath();
+ private string tmpdir = null;
+ private HashSet<string> noLogValues = new HashSet<string>();
+ private List<string> optionsContext = new List<string>();
+ private List<string> warnings = new List<string>();
+ private List<Dictionary<string, string>> deprecations = new List<Dictionary<string, string>>();
+ private List<string> cleanupFiles = new List<string>();
+
+ private Dictionary<string, string> passVars = new Dictionary<string, string>()
+ {
+ // null values means no mapping, not used in Ansible.Basic.AnsibleModule
+ { "check_mode", "CheckMode" },
+ { "debug", "DebugMode" },
+ { "diff", "DiffMode" },
+ { "keep_remote_files", "KeepRemoteFiles" },
+ { "module_name", "ModuleName" },
+ { "no_log", "NoLog" },
+ { "remote_tmp", "remoteTmp" },
+ { "selinux_special_fs", null },
+ { "shell_executable", null },
+ { "socket", null },
+ { "string_conversion_action", null },
+ { "syslog_facility", null },
+ { "tmpdir", "tmpdir" },
+ { "verbosity", "Verbosity" },
+ { "version", "AnsibleVersion" },
+ };
+ private List<string> passBools = new List<string>() { "check_mode", "debug", "diff", "keep_remote_files", "no_log" };
+ private List<string> passInts = new List<string>() { "verbosity" };
+ private Dictionary<string, List<object>> specDefaults = new Dictionary<string, List<object>>()
+ {
+ // key - (default, type) - null is freeform
+ { "apply_defaults", new List<object>() { false, typeof(bool) } },
+ { "aliases", new List<object>() { typeof(List<string>), typeof(List<string>) } },
+ { "choices", new List<object>() { typeof(List<object>), typeof(List<object>) } },
+ { "default", new List<object>() { null, null } },
+ { "deprecated_aliases", new List<object>() { typeof(List<Hashtable>), typeof(List<Hashtable>) } },
+ { "elements", new List<object>() { null, null } },
+ { "mutually_exclusive", new List<object>() { typeof(List<List<string>>), typeof(List<object>) } },
+ { "no_log", new List<object>() { false, typeof(bool) } },
+ { "options", new List<object>() { typeof(Hashtable), typeof(Hashtable) } },
+ { "removed_in_version", new List<object>() { null, typeof(string) } },
+ { "removed_at_date", new List<object>() { null, typeof(DateTime) } },
+ { "removed_from_collection", new List<object>() { null, typeof(string) } },
+ { "required", new List<object>() { false, typeof(bool) } },
+ { "required_by", new List<object>() { typeof(Hashtable), typeof(Hashtable) } },
+ { "required_if", new List<object>() { typeof(List<List<object>>), typeof(List<object>) } },
+ { "required_one_of", new List<object>() { typeof(List<List<string>>), typeof(List<object>) } },
+ { "required_together", new List<object>() { typeof(List<List<string>>), typeof(List<object>) } },
+ { "supports_check_mode", new List<object>() { false, typeof(bool) } },
+ { "type", new List<object>() { "str", null } },
+ };
+ private Dictionary<string, Delegate> optionTypes = new Dictionary<string, Delegate>()
+ {
+ { "bool", new Func<object, bool>(ParseBool) },
+ { "dict", new Func<object, Dictionary<string, object>>(ParseDict) },
+ { "float", new Func<object, float>(ParseFloat) },
+ { "int", new Func<object, int>(ParseInt) },
+ { "json", new Func<object, string>(ParseJson) },
+ { "list", new Func<object, List<object>>(ParseList) },
+ { "path", new Func<object, string>(ParsePath) },
+ { "raw", new Func<object, object>(ParseRaw) },
+ { "sid", new Func<object, SecurityIdentifier>(ParseSid) },
+ { "str", new Func<object, string>(ParseStr) },
+ };
+
+ public Dictionary<string, object> Diff = new Dictionary<string, object>();
+ public IDictionary Params = null;
+ public Dictionary<string, object> Result = new Dictionary<string, object>() { { "changed", false } };
+
+ public bool CheckMode { get; private set; }
+ public bool DebugMode { get; private set; }
+ public bool DiffMode { get; private set; }
+ public bool KeepRemoteFiles { get; private set; }
+ public string ModuleName { get; private set; }
+ public bool NoLog { get; private set; }
+ public int Verbosity { get; private set; }
+ public string AnsibleVersion { get; private set; }
+
+ public string Tmpdir
+ {
+ get
+ {
+ if (tmpdir == null)
+ {
+ SecurityIdentifier user = WindowsIdentity.GetCurrent().User;
+ DirectorySecurity dirSecurity = new DirectorySecurity();
+ dirSecurity.SetOwner(user);
+ dirSecurity.SetAccessRuleProtection(true, false); // disable inheritance rules
+ FileSystemAccessRule ace = new FileSystemAccessRule(user, FileSystemRights.FullControl,
+ InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
+ PropagationFlags.None, AccessControlType.Allow);
+ dirSecurity.AddAccessRule(ace);
+
+ string baseDir = Path.GetFullPath(Environment.ExpandEnvironmentVariables(remoteTmp));
+ if (!Directory.Exists(baseDir))
+ {
+ string failedMsg = null;
+ try
+ {
+#if CORECLR
+ DirectoryInfo createdDir = Directory.CreateDirectory(baseDir);
+ FileSystemAclExtensions.SetAccessControl(createdDir, dirSecurity);
+#else
+ Directory.CreateDirectory(baseDir, dirSecurity);
+#endif
+ }
+ catch (Exception e)
+ {
+ failedMsg = String.Format("Failed to create base tmpdir '{0}': {1}", baseDir, e.Message);
+ }
+
+ if (failedMsg != null)
+ {
+ string envTmp = Path.GetTempPath();
+ Warn(String.Format("Unable to use '{0}' as temporary directory, falling back to system tmp '{1}': {2}", baseDir, envTmp, failedMsg));
+ baseDir = envTmp;
+ }
+ else
+ {
+ NTAccount currentUser = (NTAccount)user.Translate(typeof(NTAccount));
+ string warnMsg = String.Format("Module remote_tmp {0} did not exist and was created with FullControl to {1}, ", baseDir, currentUser.ToString());
+ warnMsg += "this may cause issues when running as another user. To avoid this, create the remote_tmp dir with the correct permissions manually";
+ Warn(warnMsg);
+ }
+ }
+
+ string dateTime = DateTime.Now.ToFileTime().ToString();
+ string dirName = String.Format("ansible-moduletmp-{0}-{1}", dateTime, new Random().Next(0, int.MaxValue));
+ string newTmpdir = Path.Combine(baseDir, dirName);
+#if CORECLR
+ DirectoryInfo tmpdirInfo = Directory.CreateDirectory(newTmpdir);
+ FileSystemAclExtensions.SetAccessControl(tmpdirInfo, dirSecurity);
+#else
+ Directory.CreateDirectory(newTmpdir, dirSecurity);
+#endif
+ tmpdir = newTmpdir;
+
+ if (!KeepRemoteFiles)
+ cleanupFiles.Add(tmpdir);
+ }
+ return tmpdir;
+ }
+ }
+
+ public AnsibleModule(string[] args, IDictionary argumentSpec, IDictionary[] fragments = null)
+ {
+ // NoLog is not set yet, we cannot rely on FailJson to sanitize the output
+ // Do the minimum amount to get this running before we actually parse the params
+ Dictionary<string, string> aliases = new Dictionary<string, string>();
+ try
+ {
+ ValidateArgumentSpec(argumentSpec);
+
+ // Merge the fragments if present into the main arg spec.
+ if (fragments != null)
+ {
+ foreach (IDictionary fragment in fragments)
+ {
+ ValidateArgumentSpec(fragment);
+ MergeFragmentSpec(argumentSpec, fragment);
+ }
+ }
+
+ // Used by ansible-test to retrieve the module argument spec, not designed for public use.
+ if (_DebugArgSpec)
+ {
+ // Cannot call exit here because it will be caught with the catch (Exception e) below. Instead
+ // just throw a new exception with a specific message and the exception block will handle it.
+ ScriptBlock.Create("Set-Variable -Name ansibleTestArgSpec -Value $args[0] -Scope Global"
+ ).Invoke(argumentSpec);
+ throw new Exception("ansible-test validate-modules check");
+ }
+
+ // Now make sure all the metadata keys are set to their defaults, this must be done after we've
+ // potentially output the arg spec for ansible-test.
+ SetArgumentSpecDefaults(argumentSpec);
+
+ Params = GetParams(args);
+ aliases = GetAliases(argumentSpec, Params);
+ SetNoLogValues(argumentSpec, Params);
+ }
+ catch (Exception e)
+ {
+ if (e.Message == "ansible-test validate-modules check")
+ Exit(0);
+
+ Dictionary<string, object> result = new Dictionary<string, object>
+ {
+ { "failed", true },
+ { "msg", String.Format("internal error: {0}", e.Message) },
+ { "exception", e.ToString() }
+ };
+ WriteLine(ToJson(result));
+ Exit(1);
+ }
+
+ // Initialise public properties to the defaults before we parse the actual inputs
+ CheckMode = false;
+ DebugMode = false;
+ DiffMode = false;
+ KeepRemoteFiles = false;
+ ModuleName = "undefined win module";
+ NoLog = (bool)argumentSpec["no_log"];
+ Verbosity = 0;
+ AppDomain.CurrentDomain.ProcessExit += CleanupFiles;
+
+ List<string> legalInputs = passVars.Keys.Select(v => "_ansible_" + v).ToList();
+ legalInputs.AddRange(((IDictionary)argumentSpec["options"]).Keys.Cast<string>().ToList());
+ legalInputs.AddRange(aliases.Keys.Cast<string>().ToList());
+ CheckArguments(argumentSpec, Params, legalInputs);
+
+ // Set a Ansible friendly invocation value in the result object
+ Dictionary<string, object> invocation = new Dictionary<string, object>() { { "module_args", Params } };
+ Result["invocation"] = RemoveNoLogValues(invocation, noLogValues);
+
+ if (!NoLog)
+ LogEvent(String.Format("Invoked with:\r\n {0}", FormatLogData(Params, 2)), sanitise: false);
+ }
+
+ public static AnsibleModule Create(string[] args, IDictionary argumentSpec, IDictionary[] fragments = null)
+ {
+ return new AnsibleModule(args, argumentSpec, fragments);
+ }
+
+ public void Debug(string message)
+ {
+ if (DebugMode)
+ LogEvent(String.Format("[DEBUG] {0}", message));
+ }
+
+ public void Deprecate(string message, string version)
+ {
+ Deprecate(message, version, null);
+ }
+
+ public void Deprecate(string message, string version, string collectionName)
+ {
+ deprecations.Add(new Dictionary<string, string>() {
+ { "msg", message }, { "version", version }, { "collection_name", collectionName } });
+ LogEvent(String.Format("[DEPRECATION WARNING] {0} {1}", message, version));
+ }
+
+ public void Deprecate(string message, DateTime date)
+ {
+ Deprecate(message, date, null);
+ }
+
+ public void Deprecate(string message, DateTime date, string collectionName)
+ {
+ string isoDate = date.ToString("yyyy-MM-dd");
+ deprecations.Add(new Dictionary<string, string>() {
+ { "msg", message }, { "date", isoDate }, { "collection_name", collectionName } });
+ LogEvent(String.Format("[DEPRECATION WARNING] {0} {1}", message, isoDate));
+ }
+
+ public void ExitJson()
+ {
+ WriteLine(GetFormattedResults(Result));
+ CleanupFiles(null, null);
+ Exit(0);
+ }
+
+ public void FailJson(string message) { FailJson(message, null, null); }
+ public void FailJson(string message, ErrorRecord psErrorRecord) { FailJson(message, psErrorRecord, null); }
+ public void FailJson(string message, Exception exception) { FailJson(message, null, exception); }
+ private void FailJson(string message, ErrorRecord psErrorRecord, Exception exception)
+ {
+ Result["failed"] = true;
+ Result["msg"] = RemoveNoLogValues(message, noLogValues);
+
+
+ if (!Result.ContainsKey("exception") && (Verbosity > 2 || DebugMode))
+ {
+ if (psErrorRecord != null)
+ {
+ string traceback = String.Format("{0}\r\n{1}", psErrorRecord.ToString(), psErrorRecord.InvocationInfo.PositionMessage);
+ traceback += String.Format("\r\n + CategoryInfo : {0}", psErrorRecord.CategoryInfo.ToString());
+ traceback += String.Format("\r\n + FullyQualifiedErrorId : {0}", psErrorRecord.FullyQualifiedErrorId.ToString());
+ traceback += String.Format("\r\n\r\nScriptStackTrace:\r\n{0}", psErrorRecord.ScriptStackTrace);
+ Result["exception"] = traceback;
+ }
+ else if (exception != null)
+ Result["exception"] = exception.ToString();
+ }
+
+ WriteLine(GetFormattedResults(Result));
+ CleanupFiles(null, null);
+ Exit(1);
+ }
+
+ public void LogEvent(string message, EventLogEntryType logEntryType = EventLogEntryType.Information, bool sanitise = true)
+ {
+ // non-Windows hack; event log is not supported, not implementing a x-plat compat logger at this time
+ // original content left as comment, because it may make it easier to update this
+ return;
+ /*
+ if (NoLog)
+ return;
+
+ string logSource = "Ansible";
+ bool logSourceExists = false;
+ try
+ {
+ logSourceExists = EventLog.SourceExists(logSource);
+ }
+ catch (System.Security.SecurityException) { } // non admin users may not have permission
+
+ if (!logSourceExists)
+ {
+ try
+ {
+ EventLog.CreateEventSource(logSource, "Application");
+ }
+ catch (System.Security.SecurityException)
+ {
+ // Cannot call Warn as that calls LogEvent and we get stuck in a loop
+ warnings.Add(String.Format("Access error when creating EventLog source {0}, logging to the Application source instead", logSource));
+ logSource = "Application";
+ }
+ }
+ if (sanitise)
+ message = (string)RemoveNoLogValues(message, noLogValues);
+ message = String.Format("{0} - {1}", ModuleName, message);
+
+ using (EventLog eventLog = new EventLog("Application"))
+ {
+ eventLog.Source = logSource;
+ try
+ {
+ eventLog.WriteEntry(message, logEntryType, 0);
+ }
+ catch (System.InvalidOperationException) { } // Ignore permission errors on the Application event log
+ catch (System.Exception e)
+ {
+ // Cannot call Warn as that calls LogEvent and we get stuck in a loop
+ warnings.Add(String.Format("Unknown error when creating event log entry: {0}", e.Message));
+ }
+ }
+ */
+ }
+
+ public void Warn(string message)
+ {
+ warnings.Add(message);
+ LogEvent(String.Format("[WARNING] {0}", message), EventLogEntryType.Warning);
+ }
+
+ public static object FromJson(string json) { return FromJson<object>(json); }
+ public static T FromJson<T>(string json)
+ {
+#if CORECLR
+ return JsonConvert.DeserializeObject<T>(json);
+#else
+ JavaScriptSerializer jss = new JavaScriptSerializer();
+ jss.MaxJsonLength = int.MaxValue;
+ jss.RecursionLimit = int.MaxValue;
+ return jss.Deserialize<T>(json);
+#endif
+ }
+
+ public static string ToJson(object obj)
+ {
+ // Using PowerShell to serialize the JSON is preferable over the native .NET libraries as it handles
+ // PS Objects a lot better than the alternatives. In case we are debugging in Visual Studio we have a
+ // fallback to the other libraries as we won't be dealing with PowerShell objects there.
+ if (Runspace.DefaultRunspace != null)
+ {
+ PSObject rawOut = ScriptBlock.Create("ConvertTo-Json -InputObject $args[0] -Depth 99 -Compress").Invoke(obj)[0];
+ return rawOut.BaseObject as string;
+ }
+ else
+ {
+#if CORECLR
+ return JsonConvert.SerializeObject(obj);
+#else
+ JavaScriptSerializer jss = new JavaScriptSerializer();
+ jss.MaxJsonLength = int.MaxValue;
+ jss.RecursionLimit = int.MaxValue;
+ return jss.Serialize(obj);
+#endif
+ }
+ }
+
+ public static IDictionary GetParams(string[] args)
+ {
+ if (args.Length > 0)
+ {
+ string inputJson = File.ReadAllText(args[0]);
+ Dictionary<string, object> rawParams = FromJson<Dictionary<string, object>>(inputJson);
+ if (!rawParams.ContainsKey("ANSIBLE_MODULE_ARGS"))
+ throw new ArgumentException("Module was unable to get ANSIBLE_MODULE_ARGS value from the argument path json");
+ return (IDictionary)rawParams["ANSIBLE_MODULE_ARGS"];
+ }
+ else
+ {
+ // $complex_args is already a Hashtable, no need to waste time converting to a dictionary
+ PSObject rawArgs = ScriptBlock.Create("$complex_args").Invoke()[0];
+ return rawArgs.BaseObject as Hashtable;
+ }
+ }
+
+ public static bool ParseBool(object value)
+ {
+ if (value.GetType() == typeof(bool))
+ return (bool)value;
+
+ List<string> booleans = new List<string>();
+ booleans.AddRange(BOOLEANS_TRUE);
+ booleans.AddRange(BOOLEANS_FALSE);
+
+ string stringValue = ParseStr(value).ToLowerInvariant().Trim();
+ if (BOOLEANS_TRUE.Contains(stringValue))
+ return true;
+ else if (BOOLEANS_FALSE.Contains(stringValue))
+ return false;
+
+ string msg = String.Format("The value '{0}' is not a valid boolean. Valid booleans include: {1}",
+ stringValue, String.Join(", ", booleans));
+ throw new ArgumentException(msg);
+ }
+
+ public static Dictionary<string, object> ParseDict(object value)
+ {
+ Type valueType = value.GetType();
+ if (valueType == typeof(Dictionary<string, object>))
+ return (Dictionary<string, object>)value;
+ else if (value is IDictionary)
+ return ((IDictionary)value).Cast<DictionaryEntry>().ToDictionary(kvp => (string)kvp.Key, kvp => kvp.Value);
+ else if (valueType == typeof(string))
+ {
+ string stringValue = (string)value;
+ if (stringValue.StartsWith("{") && stringValue.EndsWith("}"))
+ return FromJson<Dictionary<string, object>>((string)value);
+ else if (stringValue.IndexOfAny(new char[1] { '=' }) != -1)
+ {
+ List<string> fields = new List<string>();
+ List<char> fieldBuffer = new List<char>();
+ char? inQuote = null;
+ bool inEscape = false;
+ string field;
+
+ foreach (char c in stringValue.ToCharArray())
+ {
+ if (inEscape)
+ {
+ fieldBuffer.Add(c);
+ inEscape = false;
+ }
+ else if (c == '\\')
+ inEscape = true;
+ else if (inQuote == null && (c == '\'' || c == '"'))
+ inQuote = c;
+ else if (inQuote != null && c == inQuote)
+ inQuote = null;
+ else if (inQuote == null && (c == ',' || c == ' '))
+ {
+ field = String.Join("", fieldBuffer);
+ if (field != "")
+ fields.Add(field);
+ fieldBuffer = new List<char>();
+ }
+ else
+ fieldBuffer.Add(c);
+ }
+
+ field = String.Join("", fieldBuffer);
+ if (field != "")
+ fields.Add(field);
+
+ return fields.Distinct().Select(i => i.Split(new[] { '=' }, 2)).ToDictionary(i => i[0], i => i.Length > 1 ? (object)i[1] : null);
+ }
+ else
+ throw new ArgumentException("string cannot be converted to a dict, must either be a JSON string or in the key=value form");
+ }
+
+ throw new ArgumentException(String.Format("{0} cannot be converted to a dict", valueType.FullName));
+ }
+
+ public static float ParseFloat(object value)
+ {
+ if (value.GetType() == typeof(float))
+ return (float)value;
+
+ string valueStr = ParseStr(value);
+ return float.Parse(valueStr);
+ }
+
+ public static int ParseInt(object value)
+ {
+ Type valueType = value.GetType();
+ if (valueType == typeof(int))
+ return (int)value;
+ else
+ return Int32.Parse(ParseStr(value));
+ }
+
+ public static string ParseJson(object value)
+ {
+ // mostly used to ensure a dict is a json string as it may
+ // have been converted on the controller side
+ Type valueType = value.GetType();
+ if (value is IDictionary)
+ return ToJson(value);
+ else if (valueType == typeof(string))
+ return (string)value;
+ else
+ throw new ArgumentException(String.Format("{0} cannot be converted to json", valueType.FullName));
+ }
+
+ public static List<object> ParseList(object value)
+ {
+ if (value == null)
+ return null;
+
+ Type valueType = value.GetType();
+ if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(List<>))
+ return (List<object>)value;
+ else if (valueType == typeof(ArrayList))
+ return ((ArrayList)value).Cast<object>().ToList();
+ else if (valueType.IsArray)
+ return ((object[])value).ToList();
+ else if (valueType == typeof(string))
+ return ((string)value).Split(',').Select(s => s.Trim()).ToList<object>();
+ else if (valueType == typeof(int))
+ return new List<object>() { value };
+ else
+ throw new ArgumentException(String.Format("{0} cannot be converted to a list", valueType.FullName));
+ }
+
+ public static string ParsePath(object value)
+ {
+ string stringValue = ParseStr(value);
+
+ // do not validate, expand the env vars if it starts with \\?\ as
+ // it is a special path designed for the NT kernel to interpret
+ if (stringValue.StartsWith(@"\\?\"))
+ return stringValue;
+
+ stringValue = Environment.ExpandEnvironmentVariables(stringValue);
+ if (stringValue.IndexOfAny(Path.GetInvalidPathChars()) != -1)
+ throw new ArgumentException("string value contains invalid path characters, cannot convert to path");
+
+ // will fire an exception if it contains any invalid chars
+ Path.GetFullPath(stringValue);
+ return stringValue;
+ }
+
+ public static object ParseRaw(object value) { return value; }
+
+ public static SecurityIdentifier ParseSid(object value)
+ {
+ string stringValue = ParseStr(value);
+
+ try
+ {
+ return new SecurityIdentifier(stringValue);
+ }
+ catch (ArgumentException) { } // ignore failures string may not have been a SID
+
+ NTAccount account = new NTAccount(stringValue);
+ return (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier));
+ }
+
+ public static string ParseStr(object value) { return value.ToString(); }
+
+ private void ValidateArgumentSpec(IDictionary argumentSpec)
+ {
+ Dictionary<string, object> changedValues = new Dictionary<string, object>();
+ foreach (DictionaryEntry entry in argumentSpec)
+ {
+ string key = (string)entry.Key;
+
+ // validate the key is a valid argument spec key
+ if (!specDefaults.ContainsKey(key))
+ {
+ string msg = String.Format("argument spec entry contains an invalid key '{0}', valid keys: {1}",
+ key, String.Join(", ", specDefaults.Keys));
+ throw new ArgumentException(FormatOptionsContext(msg, " - "));
+ }
+
+ // ensure the value is casted to the type we expect
+ Type optionType = null;
+ if (entry.Value != null)
+ optionType = (Type)specDefaults[key][1];
+ if (optionType != null)
+ {
+ Type actualType = entry.Value.GetType();
+ bool invalid = false;
+ if (optionType.IsGenericType && optionType.GetGenericTypeDefinition() == typeof(List<>))
+ {
+ // verify the actual type is not just a single value of the list type
+ Type entryType = optionType.GetGenericArguments()[0];
+ object[] arrayElementTypes = new object[]
+ {
+ null, // ArrayList does not have an ElementType
+ entryType,
+ typeof(object), // Hope the object is actually entryType or it can at least be casted.
+ };
+
+ bool isArray = entry.Value is IList && arrayElementTypes.Contains(actualType.GetElementType());
+ if (actualType == entryType || isArray)
+ {
+ object rawArray;
+ if (isArray)
+ rawArray = entry.Value;
+ else
+ rawArray = new object[1] { entry.Value };
+
+ MethodInfo castMethod = typeof(Enumerable).GetMethod("Cast").MakeGenericMethod(entryType);
+ MethodInfo toListMethod = typeof(Enumerable).GetMethod("ToList").MakeGenericMethod(entryType);
+
+ var enumerable = castMethod.Invoke(null, new object[1] { rawArray });
+ var newList = toListMethod.Invoke(null, new object[1] { enumerable });
+ changedValues.Add(key, newList);
+ }
+ else if (actualType != optionType && !(actualType == typeof(List<object>)))
+ invalid = true;
+ }
+ else
+ invalid = actualType != optionType;
+
+ if (invalid)
+ {
+ string msg = String.Format("argument spec for '{0}' did not match expected type {1}: actual type {2}",
+ key, optionType.FullName, actualType.FullName);
+ throw new ArgumentException(FormatOptionsContext(msg, " - "));
+ }
+ }
+
+ // recursively validate the spec
+ if (key == "options" && entry.Value != null)
+ {
+ IDictionary optionsSpec = (IDictionary)entry.Value;
+ foreach (DictionaryEntry optionEntry in optionsSpec)
+ {
+ optionsContext.Add((string)optionEntry.Key);
+ IDictionary optionMeta = (IDictionary)optionEntry.Value;
+ ValidateArgumentSpec(optionMeta);
+ optionsContext.RemoveAt(optionsContext.Count - 1);
+ }
+ }
+
+ // validate the type and elements key type values are known types
+ if (key == "type" || key == "elements" && entry.Value != null)
+ {
+ Type valueType = entry.Value.GetType();
+ if (valueType == typeof(string))
+ {
+ string typeValue = (string)entry.Value;
+ if (!optionTypes.ContainsKey(typeValue))
+ {
+ string msg = String.Format("{0} '{1}' is unsupported", key, typeValue);
+ msg = String.Format("{0}. Valid types are: {1}", FormatOptionsContext(msg, " - "), String.Join(", ", optionTypes.Keys));
+ throw new ArgumentException(msg);
+ }
+ }
+ else if (!(entry.Value is Delegate))
+ {
+ string msg = String.Format("{0} must either be a string or delegate, was: {1}", key, valueType.FullName);
+ throw new ArgumentException(FormatOptionsContext(msg, " - "));
+ }
+ }
+ }
+
+ // Outside of the spec iterator, change the values that were casted above
+ foreach (KeyValuePair<string, object> changedValue in changedValues)
+ argumentSpec[changedValue.Key] = changedValue.Value;
+ }
+
+ private void MergeFragmentSpec(IDictionary argumentSpec, IDictionary fragment)
+ {
+ foreach (DictionaryEntry fragmentEntry in fragment)
+ {
+ string fragmentKey = fragmentEntry.Key.ToString();
+
+ if (argumentSpec.Contains(fragmentKey))
+ {
+ // We only want to add new list entries and merge dictionary new keys and values. Leave the other
+ // values as is in the argument spec as that takes priority over the fragment.
+ if (fragmentEntry.Value is IDictionary)
+ {
+ MergeFragmentSpec((IDictionary)argumentSpec[fragmentKey], (IDictionary)fragmentEntry.Value);
+ }
+ else if (fragmentEntry.Value is IList)
+ {
+ IList specValue = (IList)argumentSpec[fragmentKey];
+ foreach (object fragmentValue in (IList)fragmentEntry.Value)
+ specValue.Add(fragmentValue);
+ }
+ }
+ else
+ argumentSpec[fragmentKey] = fragmentEntry.Value;
+ }
+ }
+
+ private void SetArgumentSpecDefaults(IDictionary argumentSpec)
+ {
+ foreach (KeyValuePair<string, List<object>> metadataEntry in specDefaults)
+ {
+ List<object> defaults = metadataEntry.Value;
+ object defaultValue = defaults[0];
+ if (defaultValue != null && defaultValue.GetType() == typeof(Type).GetType())
+ defaultValue = Activator.CreateInstance((Type)defaultValue);
+
+ if (!argumentSpec.Contains(metadataEntry.Key))
+ argumentSpec[metadataEntry.Key] = defaultValue;
+ }
+
+ // Recursively set the defaults for any inner options.
+ foreach (DictionaryEntry entry in argumentSpec)
+ {
+ if (entry.Value == null || entry.Key.ToString() != "options")
+ continue;
+
+ IDictionary optionsSpec = (IDictionary)entry.Value;
+ foreach (DictionaryEntry optionEntry in optionsSpec)
+ {
+ optionsContext.Add((string)optionEntry.Key);
+ IDictionary optionMeta = (IDictionary)optionEntry.Value;
+ SetArgumentSpecDefaults(optionMeta);
+ optionsContext.RemoveAt(optionsContext.Count - 1);
+ }
+ }
+ }
+
+ private Dictionary<string, string> GetAliases(IDictionary argumentSpec, IDictionary parameters)
+ {
+ Dictionary<string, string> aliasResults = new Dictionary<string, string>();
+
+ foreach (DictionaryEntry entry in (IDictionary)argumentSpec["options"])
+ {
+ string k = (string)entry.Key;
+ Hashtable v = (Hashtable)entry.Value;
+
+ List<string> aliases = (List<string>)v["aliases"];
+ object defaultValue = v["default"];
+ bool required = (bool)v["required"];
+
+ if (defaultValue != null && required)
+ throw new ArgumentException(String.Format("required and default are mutually exclusive for {0}", k));
+
+ foreach (string alias in aliases)
+ {
+ aliasResults.Add(alias, k);
+ if (parameters.Contains(alias))
+ parameters[k] = parameters[alias];
+ }
+
+ List<Hashtable> deprecatedAliases = (List<Hashtable>)v["deprecated_aliases"];
+ foreach (Hashtable depInfo in deprecatedAliases)
+ {
+ foreach (string keyName in new List<string> { "name" })
+ {
+ if (!depInfo.ContainsKey(keyName))
+ {
+ string msg = String.Format("{0} is required in a deprecated_aliases entry", keyName);
+ throw new ArgumentException(FormatOptionsContext(msg, " - "));
+ }
+ }
+ if (!depInfo.ContainsKey("version") && !depInfo.ContainsKey("date"))
+ {
+ string msg = "One of version or date is required in a deprecated_aliases entry";
+ throw new ArgumentException(FormatOptionsContext(msg, " - "));
+ }
+ if (depInfo.ContainsKey("version") && depInfo.ContainsKey("date"))
+ {
+ string msg = "Only one of version or date is allowed in a deprecated_aliases entry";
+ throw new ArgumentException(FormatOptionsContext(msg, " - "));
+ }
+ if (depInfo.ContainsKey("date") && depInfo["date"].GetType() != typeof(DateTime))
+ {
+ string msg = "A deprecated_aliases date must be a DateTime object";
+ throw new ArgumentException(FormatOptionsContext(msg, " - "));
+ }
+ string collectionName = null;
+ if (depInfo.ContainsKey("collection_name"))
+ {
+ collectionName = (string)depInfo["collection_name"];
+ }
+ string aliasName = (string)depInfo["name"];
+
+ if (parameters.Contains(aliasName))
+ {
+ string msg = String.Format("Alias '{0}' is deprecated. See the module docs for more information", aliasName);
+ if (depInfo.ContainsKey("version"))
+ {
+ string depVersion = (string)depInfo["version"];
+ Deprecate(FormatOptionsContext(msg, " - "), depVersion, collectionName);
+ }
+ if (depInfo.ContainsKey("date"))
+ {
+ DateTime depDate = (DateTime)depInfo["date"];
+ Deprecate(FormatOptionsContext(msg, " - "), depDate, collectionName);
+ }
+ }
+ }
+ }
+
+ return aliasResults;
+ }
+
+ private void SetNoLogValues(IDictionary argumentSpec, IDictionary parameters)
+ {
+ foreach (DictionaryEntry entry in (IDictionary)argumentSpec["options"])
+ {
+ string k = (string)entry.Key;
+ Hashtable v = (Hashtable)entry.Value;
+
+ if ((bool)v["no_log"])
+ {
+ object noLogObject = parameters.Contains(k) ? parameters[k] : null;
+ string noLogString = noLogObject == null ? "" : noLogObject.ToString();
+ if (!String.IsNullOrEmpty(noLogString))
+ noLogValues.Add(noLogString);
+ }
+ string collectionName = null;
+ if (v.ContainsKey("removed_from_collection"))
+ {
+ collectionName = (string)v["removed_from_collection"];
+ }
+
+ object removedInVersion = v["removed_in_version"];
+ if (removedInVersion != null && parameters.Contains(k))
+ Deprecate(String.Format("Param '{0}' is deprecated. See the module docs for more information", k),
+ removedInVersion.ToString(), collectionName);
+
+ object removedAtDate = v["removed_at_date"];
+ if (removedAtDate != null && parameters.Contains(k))
+ Deprecate(String.Format("Param '{0}' is deprecated. See the module docs for more information", k),
+ (DateTime)removedAtDate, collectionName);
+ }
+ }
+
+ private void CheckArguments(IDictionary spec, IDictionary param, List<string> legalInputs)
+ {
+ // initially parse the params and check for unsupported ones and set internal vars
+ CheckUnsupportedArguments(param, legalInputs);
+
+ // Only run this check if we are at the root argument (optionsContext.Count == 0)
+ if (CheckMode && !(bool)spec["supports_check_mode"] && optionsContext.Count == 0)
+ {
+ Result["skipped"] = true;
+ Result["msg"] = String.Format("remote module ({0}) does not support check mode", ModuleName);
+ ExitJson();
+ }
+ IDictionary optionSpec = (IDictionary)spec["options"];
+
+ CheckMutuallyExclusive(param, (IList)spec["mutually_exclusive"]);
+ CheckRequiredArguments(optionSpec, param);
+
+ // set the parameter types based on the type spec value
+ foreach (DictionaryEntry entry in optionSpec)
+ {
+ string k = (string)entry.Key;
+ Hashtable v = (Hashtable)entry.Value;
+
+ object value = param.Contains(k) ? param[k] : null;
+ if (value != null)
+ {
+ // convert the current value to the wanted type
+ Delegate typeConverter;
+ string type;
+ if (v["type"].GetType() == typeof(string))
+ {
+ type = (string)v["type"];
+ typeConverter = optionTypes[type];
+ }
+ else
+ {
+ type = "delegate";
+ typeConverter = (Delegate)v["type"];
+ }
+
+ try
+ {
+ value = typeConverter.DynamicInvoke(value);
+ param[k] = value;
+ }
+ catch (Exception e)
+ {
+ string msg = String.Format("argument for {0} is of type {1} and we were unable to convert to {2}: {3}",
+ k, value.GetType(), type, e.InnerException.Message);
+ FailJson(FormatOptionsContext(msg));
+ }
+
+ // ensure it matches the choices if there are choices set
+ List<string> choices = ((List<object>)v["choices"]).Select(x => x.ToString()).Cast<string>().ToList();
+ if (choices.Count > 0)
+ {
+ List<string> values;
+ string choiceMsg;
+ if (type == "list")
+ {
+ values = ((List<object>)value).Select(x => x.ToString()).Cast<string>().ToList();
+ choiceMsg = "one or more of";
+ }
+ else
+ {
+ values = new List<string>() { value.ToString() };
+ choiceMsg = "one of";
+ }
+
+ List<string> diffList = values.Except(choices, StringComparer.OrdinalIgnoreCase).ToList();
+ List<string> caseDiffList = values.Except(choices).ToList();
+ if (diffList.Count > 0)
+ {
+ string msg = String.Format("value of {0} must be {1}: {2}. Got no match for: {3}",
+ k, choiceMsg, String.Join(", ", choices), String.Join(", ", diffList));
+ FailJson(FormatOptionsContext(msg));
+ }
+ /*
+ For now we will just silently accept case insensitive choices, uncomment this if we want to add it back in
+ else if (caseDiffList.Count > 0)
+ {
+ // For backwards compatibility with Legacy.psm1 we need to be matching choices that are not case sensitive.
+ // We will warn the user it was case insensitive and tell them this will become case sensitive in the future.
+ string msg = String.Format(
+ "value of {0} was a case insensitive match of {1}: {2}. Checking of choices will be case sensitive in a future Ansible release. Case insensitive matches were: {3}",
+ k, choiceMsg, String.Join(", ", choices), String.Join(", ", caseDiffList.Select(x => RemoveNoLogValues(x, noLogValues)))
+ );
+ Warn(FormatOptionsContext(msg));
+ }*/
+ }
+ }
+ }
+
+ CheckRequiredTogether(param, (IList)spec["required_together"]);
+ CheckRequiredOneOf(param, (IList)spec["required_one_of"]);
+ CheckRequiredIf(param, (IList)spec["required_if"]);
+ CheckRequiredBy(param, (IDictionary)spec["required_by"]);
+
+ // finally ensure all missing parameters are set to null and handle sub options
+ foreach (DictionaryEntry entry in optionSpec)
+ {
+ string k = (string)entry.Key;
+ IDictionary v = (IDictionary)entry.Value;
+
+ if (!param.Contains(k))
+ param[k] = null;
+
+ CheckSubOption(param, k, v);
+ }
+ }
+
+ private void CheckUnsupportedArguments(IDictionary param, List<string> legalInputs)
+ {
+ HashSet<string> unsupportedParameters = new HashSet<string>();
+ HashSet<string> caseUnsupportedParameters = new HashSet<string>();
+ List<string> removedParameters = new List<string>();
+
+ foreach (DictionaryEntry entry in param)
+ {
+ string paramKey = (string)entry.Key;
+ if (!legalInputs.Contains(paramKey, StringComparer.OrdinalIgnoreCase))
+ unsupportedParameters.Add(paramKey);
+ else if (!legalInputs.Contains(paramKey))
+ // For backwards compatibility we do not care about the case but we need to warn the users as this will
+ // change in a future Ansible release.
+ caseUnsupportedParameters.Add(paramKey);
+ else if (paramKey.StartsWith("_ansible_"))
+ {
+ removedParameters.Add(paramKey);
+ string key = paramKey.Replace("_ansible_", "");
+ // skip setting NoLog if NoLog is already set to true (set by the module)
+ // or there's no mapping for this key
+ if ((key == "no_log" && NoLog == true) || (passVars[key] == null))
+ continue;
+
+ object value = entry.Value;
+ if (passBools.Contains(key))
+ value = ParseBool(value);
+ else if (passInts.Contains(key))
+ value = ParseInt(value);
+
+ string propertyName = passVars[key];
+ PropertyInfo property = typeof(AnsibleModule).GetProperty(propertyName);
+ FieldInfo field = typeof(AnsibleModule).GetField(propertyName, BindingFlags.NonPublic | BindingFlags.Instance);
+ if (property != null)
+ property.SetValue(this, value, null);
+ else if (field != null)
+ field.SetValue(this, value);
+ else
+ FailJson(String.Format("implementation error: unknown AnsibleModule property {0}", propertyName));
+ }
+ }
+ foreach (string parameter in removedParameters)
+ param.Remove(parameter);
+
+ if (unsupportedParameters.Count > 0)
+ {
+ legalInputs.RemoveAll(x => passVars.Keys.Contains(x.Replace("_ansible_", "")));
+ string msg = String.Format("Unsupported parameters for ({0}) module: {1}", ModuleName, String.Join(", ", unsupportedParameters));
+ msg = String.Format("{0}. Supported parameters include: {1}", FormatOptionsContext(msg), String.Join(", ", legalInputs));
+ FailJson(msg);
+ }
+
+ /*
+ // Uncomment when we want to start warning users around options that are not a case sensitive match to the spec
+ if (caseUnsupportedParameters.Count > 0)
+ {
+ legalInputs.RemoveAll(x => passVars.Keys.Contains(x.Replace("_ansible_", "")));
+ string msg = String.Format("Parameters for ({0}) was a case insensitive match: {1}", ModuleName, String.Join(", ", caseUnsupportedParameters));
+ msg = String.Format("{0}. Module options will become case sensitive in a future Ansible release. Supported parameters include: {1}",
+ FormatOptionsContext(msg), String.Join(", ", legalInputs));
+ Warn(msg);
+ }*/
+
+ // Make sure we convert all the incorrect case params to the ones set by the module spec
+ foreach (string key in caseUnsupportedParameters)
+ {
+ string correctKey = legalInputs[legalInputs.FindIndex(s => s.Equals(key, StringComparison.OrdinalIgnoreCase))];
+ object value = param[key];
+ param.Remove(key);
+ param.Add(correctKey, value);
+ }
+ }
+
+ private void CheckMutuallyExclusive(IDictionary param, IList mutuallyExclusive)
+ {
+ if (mutuallyExclusive == null)
+ return;
+
+ foreach (object check in mutuallyExclusive)
+ {
+ List<string> mutualCheck = ((IList)check).Cast<string>().ToList();
+ int count = 0;
+ foreach (string entry in mutualCheck)
+ if (param.Contains(entry))
+ count++;
+
+ if (count > 1)
+ {
+ string msg = String.Format("parameters are mutually exclusive: {0}", String.Join(", ", mutualCheck));
+ FailJson(FormatOptionsContext(msg));
+ }
+ }
+ }
+
+ private void CheckRequiredArguments(IDictionary spec, IDictionary param)
+ {
+ List<string> missing = new List<string>();
+ foreach (DictionaryEntry entry in spec)
+ {
+ string k = (string)entry.Key;
+ Hashtable v = (Hashtable)entry.Value;
+
+ // set defaults for values not already set
+ object defaultValue = v["default"];
+ if (defaultValue != null && !param.Contains(k))
+ param[k] = defaultValue;
+
+ // check required arguments
+ bool required = (bool)v["required"];
+ if (required && !param.Contains(k))
+ missing.Add(k);
+ }
+ if (missing.Count > 0)
+ {
+ string msg = String.Format("missing required arguments: {0}", String.Join(", ", missing));
+ FailJson(FormatOptionsContext(msg));
+ }
+ }
+
+ private void CheckRequiredTogether(IDictionary param, IList requiredTogether)
+ {
+ if (requiredTogether == null)
+ return;
+
+ foreach (object check in requiredTogether)
+ {
+ List<string> requiredCheck = ((IList)check).Cast<string>().ToList();
+ List<bool> found = new List<bool>();
+ foreach (string field in requiredCheck)
+ if (param.Contains(field))
+ found.Add(true);
+ else
+ found.Add(false);
+
+ if (found.Contains(true) && found.Contains(false))
+ {
+ string msg = String.Format("parameters are required together: {0}", String.Join(", ", requiredCheck));
+ FailJson(FormatOptionsContext(msg));
+ }
+ }
+ }
+
+ private void CheckRequiredOneOf(IDictionary param, IList requiredOneOf)
+ {
+ if (requiredOneOf == null)
+ return;
+
+ foreach (object check in requiredOneOf)
+ {
+ List<string> requiredCheck = ((IList)check).Cast<string>().ToList();
+ int count = 0;
+ foreach (string field in requiredCheck)
+ if (param.Contains(field))
+ count++;
+
+ if (count == 0)
+ {
+ string msg = String.Format("one of the following is required: {0}", String.Join(", ", requiredCheck));
+ FailJson(FormatOptionsContext(msg));
+ }
+ }
+ }
+
+ private void CheckRequiredIf(IDictionary param, IList requiredIf)
+ {
+ if (requiredIf == null)
+ return;
+
+ foreach (object check in requiredIf)
+ {
+ IList requiredCheck = (IList)check;
+ List<string> missing = new List<string>();
+ List<string> missingFields = new List<string>();
+ int maxMissingCount = 1;
+ bool oneRequired = false;
+
+ if (requiredCheck.Count < 3 && requiredCheck.Count < 4)
+ FailJson(String.Format("internal error: invalid required_if value count of {0}, expecting 3 or 4 entries", requiredCheck.Count));
+ else if (requiredCheck.Count == 4)
+ oneRequired = (bool)requiredCheck[3];
+
+ string key = (string)requiredCheck[0];
+ object val = requiredCheck[1];
+ IList requirements = (IList)requiredCheck[2];
+
+ if (ParseStr(param[key]) != ParseStr(val))
+ continue;
+
+ string term = "all";
+ if (oneRequired)
+ {
+ maxMissingCount = requirements.Count;
+ term = "any";
+ }
+
+ foreach (string required in requirements.Cast<string>())
+ if (!param.Contains(required))
+ missing.Add(required);
+
+ if (missing.Count >= maxMissingCount)
+ {
+ string msg = String.Format("{0} is {1} but {2} of the following are missing: {3}",
+ key, val.ToString(), term, String.Join(", ", missing));
+ FailJson(FormatOptionsContext(msg));
+ }
+ }
+ }
+
+ private void CheckRequiredBy(IDictionary param, IDictionary requiredBy)
+ {
+ foreach (DictionaryEntry entry in requiredBy)
+ {
+ string key = (string)entry.Key;
+ if (!param.Contains(key))
+ continue;
+
+ List<string> missing = new List<string>();
+ List<string> requires = ParseList(entry.Value).Cast<string>().ToList();
+ foreach (string required in requires)
+ if (!param.Contains(required))
+ missing.Add(required);
+
+ if (missing.Count > 0)
+ {
+ string msg = String.Format("missing parameter(s) required by '{0}': {1}", key, String.Join(", ", missing));
+ FailJson(FormatOptionsContext(msg));
+ }
+ }
+ }
+
+ private void CheckSubOption(IDictionary param, string key, IDictionary spec)
+ {
+ object value = param[key];
+
+ string type;
+ if (spec["type"].GetType() == typeof(string))
+ type = (string)spec["type"];
+ else
+ type = "delegate";
+
+ string elements = null;
+ Delegate typeConverter = null;
+ if (spec["elements"] != null && spec["elements"].GetType() == typeof(string))
+ {
+ elements = (string)spec["elements"];
+ typeConverter = optionTypes[elements];
+ }
+ else if (spec["elements"] != null)
+ {
+ elements = "delegate";
+ typeConverter = (Delegate)spec["elements"];
+ }
+
+ if (!(type == "dict" || (type == "list" && elements != null)))
+ // either not a dict, or list with the elements set, so continue
+ return;
+ else if (type == "list")
+ {
+ // cast each list element to the type specified
+ if (value == null)
+ return;
+
+ List<object> newValue = new List<object>();
+ foreach (object element in (List<object>)value)
+ {
+ if (elements == "dict")
+ newValue.Add(ParseSubSpec(spec, element, key));
+ else
+ {
+ try
+ {
+ object newElement = typeConverter.DynamicInvoke(element);
+ newValue.Add(newElement);
+ }
+ catch (Exception e)
+ {
+ string msg = String.Format("argument for list entry {0} is of type {1} and we were unable to convert to {2}: {3}",
+ key, element.GetType(), elements, e.Message);
+ FailJson(FormatOptionsContext(msg));
+ }
+ }
+ }
+
+ param[key] = newValue;
+ }
+ else
+ param[key] = ParseSubSpec(spec, value, key);
+ }
+
+ private object ParseSubSpec(IDictionary spec, object value, string context)
+ {
+ bool applyDefaults = (bool)spec["apply_defaults"];
+
+ // set entry to an empty dict if apply_defaults is set
+ IDictionary optionsSpec = (IDictionary)spec["options"];
+ if (applyDefaults && optionsSpec.Keys.Count > 0 && value == null)
+ value = new Dictionary<string, object>();
+ else if (optionsSpec.Keys.Count == 0 || value == null)
+ return value;
+
+ optionsContext.Add(context);
+ Dictionary<string, object> newValue = (Dictionary<string, object>)ParseDict(value);
+ Dictionary<string, string> aliases = GetAliases(spec, newValue);
+ SetNoLogValues(spec, newValue);
+
+ List<string> subLegalInputs = optionsSpec.Keys.Cast<string>().ToList();
+ subLegalInputs.AddRange(aliases.Keys.Cast<string>().ToList());
+
+ CheckArguments(spec, newValue, subLegalInputs);
+ optionsContext.RemoveAt(optionsContext.Count - 1);
+ return newValue;
+ }
+
+ private string GetFormattedResults(Dictionary<string, object> result)
+ {
+ if (!result.ContainsKey("invocation"))
+ result["invocation"] = new Dictionary<string, object>() { { "module_args", RemoveNoLogValues(Params, noLogValues) } };
+
+ if (warnings.Count > 0)
+ result["warnings"] = warnings;
+
+ if (deprecations.Count > 0)
+ result["deprecations"] = deprecations;
+
+ if (Diff.Count > 0 && DiffMode)
+ result["diff"] = Diff;
+
+ return ToJson(result);
+ }
+
+ private string FormatLogData(object data, int indentLevel)
+ {
+ if (data == null)
+ return "$null";
+
+ string msg = "";
+ if (data is IList)
+ {
+ string newMsg = "";
+ foreach (object value in (IList)data)
+ {
+ string entryValue = FormatLogData(value, indentLevel + 2);
+ newMsg += String.Format("\r\n{0}- {1}", new String(' ', indentLevel), entryValue);
+ }
+ msg += newMsg;
+ }
+ else if (data is IDictionary)
+ {
+ bool start = true;
+ foreach (DictionaryEntry entry in (IDictionary)data)
+ {
+ string newMsg = FormatLogData(entry.Value, indentLevel + 2);
+ if (!start)
+ msg += String.Format("\r\n{0}", new String(' ', indentLevel));
+ msg += String.Format("{0}: {1}", (string)entry.Key, newMsg);
+ start = false;
+ }
+ }
+ else
+ msg = (string)RemoveNoLogValues(ParseStr(data), noLogValues);
+
+ return msg;
+ }
+
+ private object RemoveNoLogValues(object value, HashSet<string> noLogStrings)
+ {
+ Queue<Tuple<object, object>> deferredRemovals = new Queue<Tuple<object, object>>();
+ object newValue = RemoveValueConditions(value, noLogStrings, deferredRemovals);
+
+ while (deferredRemovals.Count > 0)
+ {
+ Tuple<object, object> data = deferredRemovals.Dequeue();
+ object oldData = data.Item1;
+ object newData = data.Item2;
+
+ if (oldData is IDictionary)
+ {
+ foreach (DictionaryEntry entry in (IDictionary)oldData)
+ {
+ object newElement = RemoveValueConditions(entry.Value, noLogStrings, deferredRemovals);
+ ((IDictionary)newData).Add((string)entry.Key, newElement);
+ }
+ }
+ else
+ {
+ foreach (object element in (IList)oldData)
+ {
+ object newElement = RemoveValueConditions(element, noLogStrings, deferredRemovals);
+ ((IList)newData).Add(newElement);
+ }
+ }
+ }
+
+ return newValue;
+ }
+
+ private object RemoveValueConditions(object value, HashSet<string> noLogStrings, Queue<Tuple<object, object>> deferredRemovals)
+ {
+ if (value == null)
+ return value;
+
+ Type valueType = value.GetType();
+ HashSet<Type> numericTypes = new HashSet<Type>
+ {
+ typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint),
+ typeof(long), typeof(ulong), typeof(decimal), typeof(double), typeof(float)
+ };
+
+ if (numericTypes.Contains(valueType) || valueType == typeof(bool))
+ {
+ string valueString = ParseStr(value);
+ if (noLogStrings.Contains(valueString))
+ return "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER";
+ foreach (string omitMe in noLogStrings)
+ if (valueString.Contains(omitMe))
+ return "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER";
+ }
+ else if (valueType == typeof(DateTime))
+ value = ((DateTime)value).ToString("o");
+ else if (value is IList)
+ {
+ List<object> newValue = new List<object>();
+ deferredRemovals.Enqueue(new Tuple<object, object>((IList)value, newValue));
+ value = newValue;
+ }
+ else if (value is IDictionary)
+ {
+ Hashtable newValue = new Hashtable();
+ deferredRemovals.Enqueue(new Tuple<object, object>((IDictionary)value, newValue));
+ value = newValue;
+ }
+ else
+ {
+ string stringValue = value.ToString();
+ if (noLogStrings.Contains(stringValue))
+ return "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER";
+ foreach (string omitMe in noLogStrings)
+ if (stringValue.Contains(omitMe))
+ return (stringValue).Replace(omitMe, "********");
+ value = stringValue;
+ }
+ return value;
+ }
+
+ private void CleanupFiles(object s, EventArgs ev)
+ {
+ foreach (string path in cleanupFiles)
+ {
+ if (File.Exists(path))
+ File.Delete(path);
+ else if (Directory.Exists(path))
+ Directory.Delete(path, true);
+ }
+ cleanupFiles = new List<string>();
+ }
+
+ private string FormatOptionsContext(string msg, string prefix = " ")
+ {
+ if (optionsContext.Count > 0)
+ msg += String.Format("{0}found in {1}", prefix, String.Join(" -> ", optionsContext));
+ return msg;
+ }
+
+ [DllImport("kernel32.dll")]
+ private static extern IntPtr GetConsoleWindow();
+
+ private static void ExitModule(int rc)
+ {
+ // When running in a Runspace Environment.Exit will kill the entire
+ // process which is not what we want, detect if we are in a
+ // Runspace and call a ScriptBlock with exit instead.
+ if (Runspace.DefaultRunspace != null)
+ ScriptBlock.Create("Set-Variable -Name LASTEXITCODE -Value $args[0] -Scope Global; exit $args[0]").Invoke(rc);
+ else
+ {
+ // Used for local debugging in Visual Studio
+ if (System.Diagnostics.Debugger.IsAttached)
+ {
+ Console.WriteLine("Press enter to continue...");
+ Console.ReadLine();
+ }
+ Environment.Exit(rc);
+ }
+ }
+
+ private static void WriteLineModule(string line)
+ {
+ Console.WriteLine(line);
+ }
+ }
+}
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/module_utils/Ansible.ModuleUtils.AddType.psm1 b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/module_utils/Ansible.ModuleUtils.AddType.psm1
new file mode 100644
index 00000000..67307026
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/module_utils/Ansible.ModuleUtils.AddType.psm1
@@ -0,0 +1,397 @@
+# Copyright (c) 2018 Ansible Project
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+Function Add-CSharpType {
+ <#
+ .SYNOPSIS
+ Compiles one or more C# scripts similar to Add-Type. This exposes
+ more configuration options that are useable within Ansible and it
+ also allows multiple C# sources to be compiled together.
+
+ .PARAMETER References
+ [String[]] A collection of C# scripts to compile together.
+
+ .PARAMETER IgnoreWarnings
+ [Switch] Whether to compile code that contains compiler warnings, by
+ default warnings will cause a compiler error.
+
+ .PARAMETER PassThru
+ [Switch] Whether to return the loaded Assembly
+
+ .PARAMETER AnsibleModule
+ [Ansible.Basic.AnsibleModule] used to derive the TempPath and Debug values.
+ TempPath is set to the Tmpdir property of the class
+ IncludeDebugInfo is set when the Ansible verbosity is >= 3
+
+ .PARAMETER TempPath
+ [String] The temporary directory in which the dynamic assembly is
+ compiled to. This file is deleted once compilation is complete.
+ Cannot be used when AnsibleModule is set. This is a no-op when
+ running on PSCore.
+
+ .PARAMETER IncludeDebugInfo
+ [Switch] Whether to include debug information in the compiled
+ assembly. Cannot be used when AnsibleModule is set. This is a no-op
+ when running on PSCore.
+
+ .PARAMETER CompileSymbols
+ [String[]] A list of symbols to be defined during compile time. These are
+ added to the existing symbols, 'CORECLR', 'WINDOWS', 'UNIX' that are set
+ conditionalls in this cmdlet.
+
+ .NOTES
+ The following features were added to control the compiling options from the
+ code itself.
+
+ * Predefined compiler SYMBOLS
+
+ * CORECLR - Added when running on PowerShell Core.
+ * WINDOWS - Added when running on Windows.
+ * UNIX - Added when running on non-Windows.
+ * X86 - Added when running on a 32-bit process (Ansible 2.10+)
+ * AMD64 - Added when running on a 64-bit process (Ansible 2.10+)
+
+ * Ignore compiler warnings inline with the following comment inline
+
+ //NoWarn -Name <rule code> [-CLR Core|Framework]
+
+ * Specify custom assembly references inline
+
+ //AssemblyReference -Name Dll.Location.dll [-CLR Core|Framework]
+
+ # Added in Ansible 2.10
+ //AssemblyReference -Type System.Type.Name [-CLR Core|Framework]
+
+ * Create automatic type accelerators to simplify long namespace names (Ansible 2.9+)
+
+ //TypeAccelerator -Name <AcceleratorName> -TypeName <Name of compiled type>
+ #>
+ param(
+ [Parameter(Mandatory = $true)][AllowEmptyCollection()][String[]]$References,
+ [Switch]$IgnoreWarnings,
+ [Switch]$PassThru,
+ [Parameter(Mandatory = $true, ParameterSetName = "Module")][Object]$AnsibleModule,
+ [Parameter(ParameterSetName = "Manual")][String]$TempPath = $env:TMP,
+ [Parameter(ParameterSetName = "Manual")][Switch]$IncludeDebugInfo,
+ [String[]]$CompileSymbols = @()
+ )
+ if ($null -eq $References -or $References.Length -eq 0) {
+ return
+ }
+
+ # define special symbols CORECLR, WINDOWS, UNIX if required
+ # the Is* variables are defined on PSCore, if absent we assume an
+ # older version of PowerShell under .NET Framework and Windows
+ $defined_symbols = [System.Collections.ArrayList]$CompileSymbols
+
+ if ([System.IntPtr]::Size -eq 4) {
+ $defined_symbols.Add('X86') > $null
+ }
+ else {
+ $defined_symbols.Add('AMD64') > $null
+ }
+
+ $is_coreclr = Get-Variable -Name IsCoreCLR -ErrorAction SilentlyContinue
+ if ($null -ne $is_coreclr) {
+ if ($is_coreclr.Value) {
+ $defined_symbols.Add("CORECLR") > $null
+ }
+ }
+ $is_windows = Get-Variable -Name IsWindows -ErrorAction SilentlyContinue
+ if ($null -ne $is_windows) {
+ if ($is_windows.Value) {
+ $defined_symbols.Add("WINDOWS") > $null
+ }
+ else {
+ $defined_symbols.Add("UNIX") > $null
+ }
+ }
+ else {
+ $defined_symbols.Add("WINDOWS") > $null
+ }
+
+ # Store any TypeAccelerators shortcuts the util wants us to set
+ $type_accelerators = [System.Collections.Generic.List`1[Hashtable]]@()
+
+ # pattern used to find referenced assemblies in the code
+ $assembly_pattern = [Regex]"//\s*AssemblyReference\s+-(?<Parameter>(Name)|(Type))\s+(?<Name>[\w.]*)(\s+-CLR\s+(?<CLR>Core|Framework))?"
+ $no_warn_pattern = [Regex]"//\s*NoWarn\s+-Name\s+(?<Name>[\w\d]*)(\s+-CLR\s+(?<CLR>Core|Framework))?"
+ $type_pattern = [Regex]"//\s*TypeAccelerator\s+-Name\s+(?<Name>[\w.]*)\s+-TypeName\s+(?<TypeName>[\w.]*)"
+
+ # PSCore vs PSDesktop use different methods to compile the code,
+ # PSCore uses Roslyn and can compile the code purely in memory
+ # without touching the disk while PSDesktop uses CodeDom and csc.exe
+ # to compile the code. We branch out here and run each
+ # distribution's method to add our C# code.
+ if ($is_coreclr) {
+ # compile the code using Roslyn on PSCore
+
+ # Include the default assemblies using the logic in Add-Type
+ # https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs
+ $assemblies = [System.Collections.Generic.HashSet`1[Microsoft.CodeAnalysis.MetadataReference]]@(
+ [Microsoft.CodeAnalysis.CompilationReference]::CreateFromFile(([System.Reflection.Assembly]::GetAssembly([PSObject])).Location)
+ )
+ $netcore_app_ref_folder = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName([PSObject].Assembly.Location), "ref")
+ $lib_assembly_location = [System.IO.Path]::GetDirectoryName([object].Assembly.Location)
+ foreach ($file in [System.IO.Directory]::EnumerateFiles($netcore_app_ref_folder, "*.dll", [System.IO.SearchOption]::TopDirectoryOnly)) {
+ $assemblies.Add([Microsoft.CodeAnalysis.MetadataReference]::CreateFromFile($file)) > $null
+ }
+
+ # loop through the references, parse as a SyntaxTree and get
+ # referenced assemblies
+ $ignore_warnings = New-Object -TypeName 'System.Collections.Generic.Dictionary`2[[String], [Microsoft.CodeAnalysis.ReportDiagnostic]]'
+ $parse_options = ([Microsoft.CodeAnalysis.CSharp.CSharpParseOptions]::Default).WithPreprocessorSymbols($defined_symbols)
+ $syntax_trees = [System.Collections.Generic.List`1[Microsoft.CodeAnalysis.SyntaxTree]]@()
+ foreach ($reference in $References) {
+ # scan through code and add any assemblies that match
+ # //AssemblyReference -Name ... [-CLR Core]
+ # //NoWarn -Name ... [-CLR Core]
+ # //TypeAccelerator -Name ... -TypeName ...
+ $assembly_matches = $assembly_pattern.Matches($reference)
+ foreach ($match in $assembly_matches) {
+ $clr = $match.Groups["CLR"].Value
+ if ($clr -and $clr -ne "Core") {
+ continue
+ }
+
+ $parameter_type = $match.Groups["Parameter"].Value
+ $assembly_path = $match.Groups["Name"].Value
+ if ($parameter_type -eq "Type") {
+ $assembly_path = ([Type]$assembly_path).Assembly.Location
+ }
+ else {
+ if (-not ([System.IO.Path]::IsPathRooted($assembly_path))) {
+ $assembly_path = Join-Path -Path $lib_assembly_location -ChildPath $assembly_path
+ }
+ }
+ $assemblies.Add([Microsoft.CodeAnalysis.MetadataReference]::CreateFromFile($assembly_path)) > $null
+ }
+ $warn_matches = $no_warn_pattern.Matches($reference)
+ foreach ($match in $warn_matches) {
+ $clr = $match.Groups["CLR"].Value
+ if ($clr -and $clr -ne "Core") {
+ continue
+ }
+ $ignore_warnings.Add($match.Groups["Name"], [Microsoft.CodeAnalysis.ReportDiagnostic]::Suppress)
+ }
+ $syntax_trees.Add([Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree]::ParseText($reference, $parse_options)) > $null
+
+ $type_matches = $type_pattern.Matches($reference)
+ foreach ($match in $type_matches) {
+ $type_accelerators.Add(@{Name = $match.Groups["Name"].Value; TypeName = $match.Groups["TypeName"].Value })
+ }
+ }
+
+ # Release seems to contain the correct line numbers compared to
+ # debug,may need to keep a closer eye on this in the future
+ $compiler_options = (New-Object -TypeName Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions -ArgumentList @(
+ [Microsoft.CodeAnalysis.OutputKind]::DynamicallyLinkedLibrary
+ )).WithOptimizationLevel([Microsoft.CodeAnalysis.OptimizationLevel]::Release)
+
+ # set warnings to error out if IgnoreWarnings is not set
+ if (-not $IgnoreWarnings.IsPresent) {
+ $compiler_options = $compiler_options.WithGeneralDiagnosticOption([Microsoft.CodeAnalysis.ReportDiagnostic]::Error)
+ $compiler_options = $compiler_options.WithSpecificDiagnosticOptions($ignore_warnings)
+ }
+
+ # create compilation object
+ $compilation = [Microsoft.CodeAnalysis.CSharp.CSharpCompilation]::Create(
+ [System.Guid]::NewGuid().ToString(),
+ $syntax_trees,
+ $assemblies,
+ $compiler_options
+ )
+
+ # Load the compiled code and pdb info, we do this so we can
+ # include line number in a stracktrace
+ $code_ms = New-Object -TypeName System.IO.MemoryStream
+ $pdb_ms = New-Object -TypeName System.IO.MemoryStream
+ try {
+ $emit_result = $compilation.Emit($code_ms, $pdb_ms)
+ if (-not $emit_result.Success) {
+ $errors = [System.Collections.ArrayList]@()
+
+ foreach ($e in $emit_result.Diagnostics) {
+ # builds the error msg, based on logic in Add-Type
+ # https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs#L1239
+ if ($null -eq $e.Location.SourceTree) {
+ $errors.Add($e.ToString()) > $null
+ continue
+ }
+
+ $cancel_token = New-Object -TypeName System.Threading.CancellationToken -ArgumentList $false
+ $text_lines = $e.Location.SourceTree.GetText($cancel_token).Lines
+ $line_span = $e.Location.GetLineSpan()
+
+ $diagnostic_message = $e.ToString()
+ $error_line_string = $text_lines[$line_span.StartLinePosition.Line].ToString()
+ $error_position = $line_span.StartLinePosition.Character
+
+ $sb = New-Object -TypeName System.Text.StringBuilder -ArgumentList ($diagnostic_message.Length + $error_line_string.Length * 2 + 4)
+ $sb.AppendLine($diagnostic_message)
+ $sb.AppendLine($error_line_string)
+
+ for ($i = 0; $i -lt $error_line_string.Length; $i++) {
+ if ([System.Char]::IsWhiteSpace($error_line_string[$i])) {
+ continue
+ }
+ $sb.Append($error_line_string, 0, $i)
+ $sb.Append(' ', [Math]::Max(0, $error_position - $i))
+ $sb.Append("^")
+ break
+ }
+
+ $errors.Add($sb.ToString()) > $null
+ }
+
+ throw [InvalidOperationException]"Failed to compile C# code:`r`n$($errors -join "`r`n")"
+ }
+
+ $code_ms.Seek(0, [System.IO.SeekOrigin]::Begin) > $null
+ $pdb_ms.Seek(0, [System.IO.SeekOrigin]::Begin) > $null
+ $compiled_assembly = [System.Runtime.Loader.AssemblyLoadContext]::Default.LoadFromStream($code_ms, $pdb_ms)
+ }
+ finally {
+ $code_ms.Close()
+ $pdb_ms.Close()
+ }
+ }
+ else {
+ # compile the code using CodeDom on PSDesktop
+
+ # configure compile options based on input
+ if ($PSCmdlet.ParameterSetName -eq "Module") {
+ $temp_path = $AnsibleModule.Tmpdir
+ $include_debug = $AnsibleModule.Verbosity -ge 3
+ }
+ else {
+ $temp_path = $TempPath
+ $include_debug = $IncludeDebugInfo.IsPresent
+ }
+ $compiler_options = [System.Collections.ArrayList]@("/optimize")
+ if ($defined_symbols.Count -gt 0) {
+ $compiler_options.Add("/define:" + ([String]::Join(";", $defined_symbols.ToArray()))) > $null
+ }
+
+ $compile_parameters = New-Object -TypeName System.CodeDom.Compiler.CompilerParameters
+ $compile_parameters.GenerateExecutable = $false
+ $compile_parameters.GenerateInMemory = $true
+ $compile_parameters.TreatWarningsAsErrors = (-not $IgnoreWarnings.IsPresent)
+ $compile_parameters.IncludeDebugInformation = $include_debug
+ $compile_parameters.TempFiles = (New-Object -TypeName System.CodeDom.Compiler.TempFileCollection -ArgumentList $temp_path, $false)
+
+ # Add-Type automatically references System.dll, System.Core.dll,
+ # and System.Management.Automation.dll which we replicate here
+ $assemblies = [System.Collections.Generic.HashSet`1[String]]@(
+ "System.dll",
+ "System.Core.dll",
+ ([System.Reflection.Assembly]::GetAssembly([PSObject])).Location
+ )
+
+ # create a code snippet for each reference and check if we need
+ # to reference any extra assemblies
+ $ignore_warnings = [System.Collections.ArrayList]@()
+ $compile_units = [System.Collections.Generic.List`1[System.CodeDom.CodeSnippetCompileUnit]]@()
+ foreach ($reference in $References) {
+ # scan through code and add any assemblies that match
+ # //AssemblyReference -Name ... [-CLR Framework]
+ # //NoWarn -Name ... [-CLR Framework]
+ # //TypeAccelerator -Name ... -TypeName ...
+ $assembly_matches = $assembly_pattern.Matches($reference)
+ foreach ($match in $assembly_matches) {
+ $clr = $match.Groups["CLR"].Value
+ if ($clr -and $clr -ne "Framework") {
+ continue
+ }
+
+ $parameter_type = $match.Groups["Parameter"].Value
+ $assembly_path = $match.Groups["Name"].Value
+ if ($parameter_type -eq "Type") {
+ $assembly_path = ([Type]$assembly_path).Assembly.Location
+ }
+ $assemblies.Add($assembly_path) > $null
+ }
+ $warn_matches = $no_warn_pattern.Matches($reference)
+ foreach ($match in $warn_matches) {
+ $clr = $match.Groups["CLR"].Value
+ if ($clr -and $clr -ne "Framework") {
+ continue
+ }
+ $warning_id = $match.Groups["Name"].Value
+ # /nowarn should only contain the numeric part
+ if ($warning_id.StartsWith("CS")) {
+ $warning_id = $warning_id.Substring(2)
+ }
+ $ignore_warnings.Add($warning_id) > $null
+ }
+ $compile_units.Add((New-Object -TypeName System.CodeDom.CodeSnippetCompileUnit -ArgumentList $reference)) > $null
+
+ $type_matches = $type_pattern.Matches($reference)
+ foreach ($match in $type_matches) {
+ $type_accelerators.Add(@{Name = $match.Groups["Name"].Value; TypeName = $match.Groups["TypeName"].Value })
+ }
+ }
+ if ($ignore_warnings.Count -gt 0) {
+ $compiler_options.Add("/nowarn:" + ([String]::Join(",", $ignore_warnings.ToArray()))) > $null
+ }
+ $compile_parameters.ReferencedAssemblies.AddRange($assemblies)
+ $compile_parameters.CompilerOptions = [String]::Join(" ", $compiler_options.ToArray())
+
+ # compile the code together and check for errors
+ $provider = New-Object -TypeName Microsoft.CSharp.CSharpCodeProvider
+
+ # This calls csc.exe which can take compiler options from environment variables. Currently these env vars
+ # are known to have problems so they are unset:
+ # LIB - additional library paths will fail the compilation if they are invalid
+ $originalEnv = @{}
+ try {
+ 'LIB' | ForEach-Object -Process {
+ $value = Get-Item -LiteralPath "Env:\$_" -ErrorAction SilentlyContinue
+ if ($value) {
+ $originalEnv[$_] = $value
+ Remove-Item -LiteralPath "Env:\$_"
+ }
+ }
+
+ $compile = $provider.CompileAssemblyFromDom($compile_parameters, $compile_units)
+ }
+ finally {
+ foreach ($kvp in $originalEnv.GetEnumerator()) {
+ [System.Environment]::SetEnvironmentVariable($kvp.Key, $kvp.Value, "Process")
+ }
+ }
+
+ if ($compile.Errors.HasErrors) {
+ $msg = "Failed to compile C# code: "
+ foreach ($e in $compile.Errors) {
+ $msg += "`r`n" + $e.ToString()
+ }
+ throw [InvalidOperationException]$msg
+ }
+ $compiled_assembly = $compile.CompiledAssembly
+ }
+
+ $type_accelerator = [PSObject].Assembly.GetType("System.Management.Automation.TypeAccelerators")
+ foreach ($accelerator in $type_accelerators) {
+ $type_name = $accelerator.TypeName
+ $found = $false
+
+ foreach ($assembly_type in $compiled_assembly.GetTypes()) {
+ if ($assembly_type.Name -eq $type_name) {
+ $type_accelerator::Add($accelerator.Name, $assembly_type)
+ $found = $true
+ break
+ }
+ }
+ if (-not $found) {
+ throw "Failed to find compiled class '$type_name' for custom TypeAccelerator."
+ }
+ }
+
+ # return the compiled assembly if PassThru is set.
+ if ($PassThru) {
+ return $compiled_assembly
+ }
+}
+
+Export-ModuleMember -Function Add-CSharpType
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/shell_plugins/pwsh.py b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/shell_plugins/pwsh.py
new file mode 100644
index 00000000..468c5406
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/shell_plugins/pwsh.py
@@ -0,0 +1,376 @@
+# Copyright (c) 2014, Chris Church <chris@ninemoreminutes.com>
+# Copyright (c) 2017 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = '''
+name: powershell
+version_added: historical
+short_description: Cross-Platform PowerShell
+description:
+- The only option when using 'winrm' or 'psrp' as a connection plugin.
+- Can also be used when using 'ssh' as a connection plugin and the C(DefaultShell) has been configured to PowerShell.
+options:
+ async_dir:
+ description:
+ - Directory in which ansible will keep async job information.
+ - Before Ansible 2.8, this was set to C(remote_tmp + "\\.ansible_async").
+ default: '%HOME%/.ansible_async'
+ ini:
+ - section: pwsh
+ key: async_dir
+ vars:
+ - name: ansible_async_dir
+ version_added: '2.8'
+ remote_tmp:
+ description:
+ - Temporary directory to use on targets when copying files to the host.
+ default: '%HOME%/.ansible/tmp'
+ ini:
+ - section: pwsh
+ key: remote_tmp
+ vars:
+ - name: ansible_remote_tmp
+ set_module_language:
+ description:
+ - Controls if we set the locale for modules when executing on the
+ target.
+ - Windows only supports C(no) as an option.
+ type: bool
+ default: 'no'
+ choices: ['no', False]
+ environment:
+ description:
+ - List of dictionaries of environment variables and their values to use when
+ executing commands.
+ type: list
+ default: [{}]
+'''
+# original uses this, but we're copying into this plugin explicitly
+# so we can override windows-isms
+#
+# extends_documentation_fragment:
+# - shell_windows
+
+import base64
+import os
+import re
+import shlex
+import pkgutil
+import xml.etree.ElementTree as ET
+import ntpath
+
+from ansible.module_utils._text import to_bytes, to_text
+from ansible.plugins.shell import ShellBase
+
+
+_common_args = ['pwsh', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Unrestricted']
+
+
+def _parse_clixml(data, stream="Error"):
+ """
+ Takes a byte string like '#< CLIXML\r\n<Objs...' and extracts the stream
+ message encoded in the XML data. CLIXML is used by PowerShell to encode
+ multiple objects in stderr.
+ """
+ lines = []
+
+ # There are some scenarios where the stderr contains a nested CLIXML element like
+ # '<# CLIXML\r\n<# CLIXML\r\n<Objs>...</Objs><Objs>...</Objs>'.
+ # Parse each individual <Objs> element and add the error strings to our stderr list.
+ # https://github.com/ansible/ansible/issues/69550
+ while data:
+ end_idx = data.find(b"</Objs>") + 7
+ current_element = data[data.find(b"<Objs "):end_idx]
+ data = data[end_idx:]
+
+ clixml = ET.fromstring(current_element)
+ namespace_match = re.match(r'{(.*)}', clixml.tag)
+ namespace = "{%s}" % namespace_match.group(1) if namespace_match else ""
+
+ strings = clixml.findall("./%sS" % namespace)
+ lines.extend([e.text.replace('_x000D__x000A_', '') for e in strings if e.attrib.get('S') == stream])
+
+ return to_bytes('\r\n'.join(lines))
+
+
+class ShellModule(ShellBase):
+
+ # Common shell filenames that this plugin handles
+ # Powershell is handled differently. It's selected when winrm is the
+ # connection
+ COMPATIBLE_SHELLS = frozenset()
+ # Family of shells this has. Must match the filename without extension
+ SHELL_FAMILY = 'pwsh'
+
+ _SHELL_REDIRECT_ALLNULL = '> $null'
+ _SHELL_AND = ';'
+
+ # Used by various parts of Ansible to do Windows specific changes
+ _IS_WINDOWS = True
+
+ # TODO: add binary module support
+
+ def env_prefix(self, **kwargs):
+ # powershell/winrm env handling is handled in the exec wrapper
+ return ""
+
+ # def join_path(self, *args):
+ # # use normpath() to remove doubled slashed and convert forward to backslashes
+ # parts = [ntpath.normpath(self._unquote(arg)) for arg in args]
+
+ # # Becuase ntpath.join treats any component that begins with a backslash as an absolute path,
+ # # we have to strip slashes from at least the beginning, otherwise join will ignore all previous
+ # # path components except for the drive.
+ # return ntpath.join(parts[0], *[part.strip('\\') for part in parts[1:]])
+
+ def get_remote_filename(self, pathname):
+ # powershell requires that script files end with .ps1
+ base_name = os.path.basename(pathname.strip())
+ name, ext = os.path.splitext(base_name.strip())
+ if ext.lower() not in ['.ps1']:
+ return name + '.ps1'
+
+ return base_name.strip()
+
+ def path_has_trailing_slash(self, path):
+ # Allow Windows paths to be specified using either slash.
+ path = self._unquote(path)
+ return path.endswith('/') or path.endswith('\\')
+
+ # def chmod(self, paths, mode):
+ # raise NotImplementedError('chmod is not implemented for Powershell')
+
+ # def chown(self, paths, user):
+ # raise NotImplementedError('chown is not implemented for Powershell')
+
+ # def set_user_facl(self, paths, user, mode):
+ # raise NotImplementedError('set_user_facl is not implemented for Powershell')
+
+ def remove(self, path, recurse=False):
+ path = self._escape(self._unquote(path))
+ if recurse:
+ return self._encode_script('''Remove-Item '%s' -Force -Recurse;''' % path)
+ else:
+ return self._encode_script('''Remove-Item '%s' -Force;''' % path)
+
+ def mkdtemp(self, basefile=None, system=False, mode=None, tmpdir=None):
+ # Windows does not have an equivalent for the system temp files, so
+ # the param is ignored
+ if not basefile:
+ basefile = self.__class__._generate_temp_dir_name()
+ basefile = self._escape(self._unquote(basefile))
+ basetmpdir = tmpdir if tmpdir else self.get_option('remote_tmp')
+
+ script = '''
+ $tmp_path = [System.Environment]::ExpandEnvironmentVariables('%s')
+ $tmp = New-Item -Type Directory -Path $tmp_path -Name '%s'
+ Write-Output -InputObject $tmp.FullName
+ ''' % (basetmpdir, basefile)
+ return self._encode_script(script.strip())
+
+ def expand_user(self, user_home_path, username=''):
+ # PowerShell only supports "~" (not "~username"). Resolve-Path ~ does
+ # not seem to work remotely, though by default we are always starting
+ # in the user's home directory.
+ user_home_path = self._unquote(user_home_path)
+ if user_home_path == '~':
+ script = 'Write-Output (Get-Location).Path'
+ elif user_home_path.startswith('~\\'):
+ script = "Write-Output ((Get-Location).Path + '%s')" % self._escape(user_home_path[1:])
+ else:
+ script = "Write-Output '%s'" % self._escape(user_home_path)
+ return self._encode_script(script)
+
+ def exists(self, path):
+ path = self._escape(self._unquote(path))
+ script = '''
+ If (Test-Path '%s')
+ {
+ $res = 0;
+ }
+ Else
+ {
+ $res = 1;
+ }
+ Write-Output '$res';
+ Exit $res;
+ ''' % path
+ return self._encode_script(script)
+
+ def checksum(self, path, *args, **kwargs):
+ path = self._escape(self._unquote(path))
+ script = '''
+ If (Test-Path -PathType Leaf '%(path)s')
+ {
+ $sp = new-object -TypeName System.Security.Cryptography.SHA1CryptoServiceProvider;
+ $fp = [System.IO.File]::Open('%(path)s', [System.IO.Filemode]::Open, [System.IO.FileAccess]::Read);
+ [System.BitConverter]::ToString($sp.ComputeHash($fp)).Replace("-", "").ToLower();
+ $fp.Dispose();
+ }
+ ElseIf (Test-Path -PathType Container '%(path)s')
+ {
+ Write-Output "3";
+ }
+ Else
+ {
+ Write-Output "1";
+ }
+ ''' % dict(path=path)
+ return self._encode_script(script)
+
+ def build_module_command(self, env_string, shebang, cmd, arg_path=None):
+ bootstrap_wrapper = pkgutil.get_data("ansible.executor.powershell", "bootstrap_wrapper.ps1")
+
+ # pipelining bypass
+ if cmd == '':
+ return self._encode_script(script=bootstrap_wrapper, strict_mode=False, preserve_rc=False)
+
+ # non-pipelining
+
+ cmd_parts = shlex.split(cmd, posix=False)
+ cmd_parts = list(map(to_text, cmd_parts))
+ if shebang and shebang.lower() == '#!powershell':
+ if not self._unquote(cmd_parts[0]).lower().endswith('.ps1'):
+ # we're running a module via the bootstrap wrapper
+ cmd_parts[0] = '"%s.ps1"' % self._unquote(cmd_parts[0])
+ # HACK begin dirty, dirty hack
+ # we need to override the built-in Ansible.Basic module util
+ # to one that will work on non-Windows platforms.
+ # But, we don't have access to the code that processes those in anything pluggable.
+ # So instead, we're going to replace the bootstrap_wrapper with one that contains a slight modification
+ # that will replace the Ansible.Basic module util contents with the contents of the one in this collection.
+ # This particular way of hacking it will only work because we're relying on the connection being local.
+ # To make this hack work on remote hosts, we'd have to also copy that file's contents or modify the payload
+ # before it made it to the remote host. The reason we can't just embed it in commands as strings is because
+ # it will be too big.
+ local_mu = os.path.join(os.path.dirname(__file__), '..', 'module_utils')
+ ansible_basic_cs = os.path.join(local_mu, 'Ansible.Basic.cs')
+ addtype_ps = os.path.join(local_mu, 'Ansible.ModuleUtils.AddType.psm1')
+ wrapper_hacked = '''
+ &chcp.com 65001 > $null
+ $exec_wrapper_str = $input | Out-String
+ $split_parts = $exec_wrapper_str.Split(@("`0`0`0`0"), 2, [StringSplitOptions]::RemoveEmptyEntries)
+ If (-not $split_parts.Length -eq 2) { throw "invalid payload" }
+ Set-Variable -Name json_raw -Value $split_parts[1]
+ # begin hack
+ ############
+ function Get-EncodedFileContents {
+ param($Path)
+
+ $enc = [System.Text.Encoding]::UTF8
+ $mustring = [System.IO.File]::ReadAllText($Path, $enc)
+ $mubytes = $enc.GetBytes($mustring)
+ $mu64 = [Convert]::ToBase64String($mubytes)
+
+ $mu64
+ }
+
+ $payload_obj = $json_raw | ConvertFrom-Json
+
+ if ($payload_obj.csharp_utils.'Ansible.Basic') {
+ $local_basic_file = '%s'
+ $payload_obj.csharp_utils.'Ansible.Basic' = Get-EncodedFileContents($local_basic_file)
+ }
+
+ if ($payload_obj.powershell_modules.'Ansible.ModuleUtils.AddType') {
+ $local_addtype_file = '%s'
+ $payload_obj.powershell_modules.'Ansible.ModuleUtils.AddType' = Get-EncodedFileContents($local_addtype_file)
+ }
+
+ $json_raw = $payload_obj | ConvertTo-Json -Depth 99
+ ##########
+ # end hack
+ $exec_wrapper = [ScriptBlock]::Create($split_parts[0])
+ &$exec_wrapper
+ ''' % (ansible_basic_cs, addtype_ps)
+ bootstrap_wrapper = wrapper_hacked
+ wrapper_cmd = "cat " + cmd_parts[0] + " | " + self._encode_script(script=bootstrap_wrapper, strict_mode=False, preserve_rc=False)
+ return wrapper_cmd
+ elif shebang and shebang.startswith('#!'):
+ cmd_parts.insert(0, shebang[2:])
+ elif not shebang:
+ # The module is assumed to be a binary
+ cmd_parts[0] = self._unquote(cmd_parts[0])
+ cmd_parts.append(arg_path)
+ script = '''
+ Try
+ {
+ %s
+ %s
+ }
+ Catch
+ {
+ $_obj = @{ failed = $true }
+ If ($_.Exception.GetType)
+ {
+ $_obj.Add('msg', $_.Exception.Message)
+ }
+ Else
+ {
+ $_obj.Add('msg', $_.ToString())
+ }
+ If ($_.InvocationInfo.PositionMessage)
+ {
+ $_obj.Add('exception', $_.InvocationInfo.PositionMessage)
+ }
+ ElseIf ($_.ScriptStackTrace)
+ {
+ $_obj.Add('exception', $_.ScriptStackTrace)
+ }
+ Try
+ {
+ $_obj.Add('error_record', ($_ | ConvertTo-Json | ConvertFrom-Json))
+ }
+ Catch
+ {
+ }
+ Echo $_obj | ConvertTo-Json -Compress -Depth 99
+ Exit 1
+ }
+ ''' % (env_string, ' '.join(cmd_parts))
+ return self._encode_script(script, preserve_rc=False)
+
+ def wrap_for_exec(self, cmd):
+ return '& %s; exit $LASTEXITCODE' % cmd
+
+ def _unquote(self, value):
+ '''Remove any matching quotes that wrap the given value.'''
+ value = to_text(value or '')
+ m = re.match(r'^\s*?\'(.*?)\'\s*?$', value)
+ if m:
+ return m.group(1)
+ m = re.match(r'^\s*?"(.*?)"\s*?$', value)
+ if m:
+ return m.group(1)
+ return value
+
+ def _escape(self, value):
+ '''Return value escaped for use in PowerShell single quotes.'''
+ # There are 5 chars that need to be escaped in a single quote.
+ # https://github.com/PowerShell/PowerShell/blob/b7cb335f03fe2992d0cbd61699de9d9aafa1d7c1/src/System.Management.Automation/engine/parser/CharTraits.cs#L265-L272
+ return re.compile(u"(['\u2018\u2019\u201a\u201b])").sub(u'\\1\\1', value)
+
+ def _encode_script(self, script, as_list=False, strict_mode=True, preserve_rc=True):
+ '''Convert a PowerShell script to a single base64-encoded command.'''
+ script = to_text(script)
+
+ if script == u'-':
+ cmd_parts = _common_args + ['-Command', '-']
+
+ else:
+ if strict_mode:
+ script = u'Set-StrictMode -Version Latest\r\n%s' % script
+ # try to propagate exit code if present- won't work with begin/process/end-style scripts (ala put_file)
+ # NB: the exit code returned may be incorrect in the case of a successful command followed by an invalid command
+ if preserve_rc:
+ script = u'%s\r\nIf (-not $?) { If (Get-Variable LASTEXITCODE -ErrorAction SilentlyContinue) { exit $LASTEXITCODE } Else { exit 1 } }\r\n'\
+ % script
+ script = '\n'.join([x.strip() for x in script.splitlines() if x.strip()])
+ encoded_script = to_text(base64.b64encode(script.encode('utf-16-le')), 'utf-8')
+ cmd_parts = _common_args + ['-EncodedCommand', encoded_script]
+
+ if as_list:
+ return cmd_parts
+ return ' '.join(cmd_parts)
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/tasks/main.yml
new file mode 100644
index 00000000..f8beefc0
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/tasks/main.yml
@@ -0,0 +1,6 @@
+---
+- name: Set the connection and shell
+ when: ansible_os_family | default('Windows') != 'Windows'
+ set_fact:
+ ansible_connection: "{{ pwsh_ansible_connection }}"
+ ansible_shell_type: "{{ pwsh_ansible_shell_type }}"
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/vars/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/vars/main.yml
new file mode 100644
index 00000000..1c61487b
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_sqlserver_test_plugins/vars/main.yml
@@ -0,0 +1,3 @@
+---
+pwsh_ansible_connection: local_pwsh
+pwsh_ansible_shell_type: pwsh
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_win_sqlserver/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_win_sqlserver/aliases
new file mode 100644
index 00000000..118c01c0
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_win_sqlserver/aliases
@@ -0,0 +1,2 @@
+windows/setup
+needs/target/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_win_sqlserver/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_win_sqlserver/meta/main.yml
new file mode 100644
index 00000000..b77b99ca
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_win_sqlserver/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_win_sqlserver/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_win_sqlserver/tasks/main.yml
new file mode 100644
index 00000000..acc1dbeb
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_win_sqlserver/tasks/main.yml
@@ -0,0 +1,8 @@
+---
+- name: Install Powershell modules
+ ansible.windows.win_shell: |
+ {{ item }}
+ no_log: "{{ ansible_verbosity | int < 3 }}"
+ loop:
+ - "{{ dbatools_install_cmd }}"
+ - "{{ dbops_install_cmd }}"
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_win_sqlserver/vars/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_win_sqlserver/vars/main.yml
new file mode 100644
index 00000000..977dc70e
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/setup_win_sqlserver/vars/main.yml
@@ -0,0 +1,2 @@
+---
+sqlserver_windows: true
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_configure/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_configure/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_configure/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_configure/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_configure/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_configure/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_configure/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_configure/tasks/main.yml
new file mode 100644
index 00000000..0b647cc6
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_configure/tasks/main.yml
@@ -0,0 +1,67 @@
+---
+- name: Var block
+ module_defaults:
+ lowlydba.sqlserver.sp_configure:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ tags: ["sp_configure"]
+ block:
+ - name: Configure
+ lowlydba.sqlserver.sp_configure:
+ name: "show advanced options"
+ value: 1
+ register: result
+ - assert:
+ that:
+ - result.data.NewValue == 1
+ - result.data.SqlInstance != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.PreviousValue != None
+
+ - name: Configure in checkmode
+ lowlydba.sqlserver.sp_configure:
+ name: "show advanced options"
+ value: 0
+ register: result
+ check_mode: true
+ - assert:
+ that:
+ - result is changed
+
+ - name: Verify unchanged configuration
+ lowlydba.sqlserver.sp_configure:
+ name: "show advanced options"
+ value: 0
+ register: result
+ - assert:
+ that:
+ - result.data.PreviousValue == 1
+ - result.data.NewValue == 0
+ - result is changed
+
+ - name: Unchanged configuration
+ lowlydba.sqlserver.sp_configure:
+ name: "show advanced options"
+ value: 0
+ register: result
+ - assert:
+ that:
+ - result is not changed
+
+ - name: Config requiring restart
+ lowlydba.sqlserver.sp_configure:
+ name: "ScanForStartupProcedures"
+ value: 1
+ register: result
+ - assert:
+ that:
+ - result is changed
+ - result.data.RestartRequired is true
+
+ always:
+ - name: Reset Option
+ lowlydba.sqlserver.sp_configure:
+ name: "show advanced options"
+ value: 0
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_whoisactive/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_whoisactive/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_whoisactive/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_whoisactive/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_whoisactive/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_whoisactive/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_whoisactive/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_whoisactive/tasks/main.yml
new file mode 100644
index 00000000..27f22670
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/sp_whoisactive/tasks/main.yml
@@ -0,0 +1,46 @@
+---
+- name: Var block
+ vars:
+ target_database: "master"
+ module_defaults:
+ lowlydba.sqlserver.sp_whoisactive:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ database: "{{ target_database }}"
+ tags: ["sp_whoisactive"]
+ block:
+ - name: Install sp_whoisactive
+ lowlydba.sqlserver.sp_whoisactive:
+ register: result
+ - assert:
+ that:
+ - result.data.Status != None
+ - result.data.SqlInstance != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.Database == "{{ target_database }}"
+ - result.data.Version != None
+ - result.data.Status in ('Installed', 'Updated')
+ - result is changed
+
+ - name: Update sp_whoisactive
+ lowlydba.sqlserver.sp_whoisactive:
+ register: result
+ - assert:
+ that:
+ - result.data.SqlInstance != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.Database == "{{ target_database }}"
+ - result.data.Version != None
+ - result.data.Status == 'Updated'
+ - result is changed
+
+ - name: Update sp_whoisactive in checkmode
+ lowlydba.sqlserver.sp_whoisactive:
+ register: result
+ check_mode: true
+ - assert:
+ that:
+ - result is changed
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/traceflag/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/traceflag/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/traceflag/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/traceflag/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/traceflag/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/traceflag/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/traceflag/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/traceflag/tasks/main.yml
new file mode 100644
index 00000000..003f4e46
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/traceflag/tasks/main.yml
@@ -0,0 +1,90 @@
+---
+- name: Var block
+ vars:
+ trace_flag: "999"
+ module_defaults:
+ lowlydba.sqlserver.traceflag:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ trace_flag: "{{ trace_flag }}"
+ tags: ["traceflag"]
+ block:
+ - name: Set a traceflag
+ lowlydba.sqlserver.traceflag:
+ enabled: true
+ register: result
+ - assert:
+ that:
+ - result.data.SqlInstance != None
+ - result.data.InstanceName != None
+ - result.data.TraceFlag == {{ trace_flag }}
+
+ - name: Enable an enabled traceflag
+ lowlydba.sqlserver.traceflag:
+ enabled: true
+ register: result
+ - assert:
+ that:
+ - result.data.SqlInstance != None
+ - result.data.InstanceName != None
+ - result.data.TraceFlag == {{ trace_flag }}
+ - result is not changed
+
+ - name: Disable a traceflag
+ lowlydba.sqlserver.traceflag:
+ enabled: false
+ register: result
+ - assert:
+ that:
+ - result.data.SqlInstance != None
+ - result.data.InstanceName != None
+ - result.data.TraceFlag == {{ trace_flag }}
+ - result is changed
+
+ - name: Disable a disabled traceflag
+ lowlydba.sqlserver.traceflag:
+ enabled: false
+ register: result
+ - assert:
+ that:
+ - result.data.SqlInstance != None
+ - result.data.InstanceName != None
+ - result.data.TraceFlag == {{ trace_flag }}
+ - result is not changed
+
+ - name: Enable a traceflag
+ lowlydba.sqlserver.traceflag:
+ enabled: true
+ register: result
+ - assert:
+ that:
+ - result.data.SqlInstance != None
+ - result.data.InstanceName != None
+ - result.data.TraceFlag == {{ trace_flag }}
+ - result is changed
+
+ - name: Disable a traceflag in checkmode
+ lowlydba.sqlserver.traceflag:
+ enabled: false
+ register: result
+ check_mode: true
+ - assert:
+ that:
+ - result is changed
+
+ - name: Verify unchanged in checkmode
+ lowlydba.sqlserver.traceflag:
+ enabled: false
+ register: result
+ - assert:
+ that:
+ - result.data.SqlInstance != None
+ - result.data.InstanceName != None
+ - result.data.TraceFlag == {{ trace_flag }}
+ - result is changed
+
+ always:
+ - name: Disable a traceflag
+ lowlydba.sqlserver.traceflag:
+ enabled: false
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/user/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/user/aliases
new file mode 100644
index 00000000..4f4b6b91
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/user/aliases
@@ -0,0 +1,2 @@
+context/target
+setup/once/setup_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/user/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/user/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/user/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/user/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/user/tasks/main.yml
new file mode 100644
index 00000000..3c377e4d
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/user/tasks/main.yml
@@ -0,0 +1,115 @@
+---
+- name: Var block
+ vars:
+ login_name: "PhillipJFry"
+ plain_password: "P0pS3cret!23$%"
+ password_expiration_enabled: false
+ password_policy_enforced: false
+ password_must_change: false
+ enabled: false
+ default_database: "master"
+ language: "us_english"
+ default_schema: "dbo"
+ username: "PhillipJFry"
+ database: "master"
+ module_defaults:
+ lowlydba.sqlserver.login:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ default_database: "{{ default_database }}"
+ login: "{{ login_name }}"
+ password: "{{ plain_password }}"
+ password_expiration_enabled: "{{ password_expiration_enabled }}"
+ password_must_change: "{{ password_must_change }}"
+ enabled: "{{ enabled }}"
+ language: "{{ language }}"
+ state: present
+ lowlydba.sqlserver.user:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ database: "{{ database }}"
+ login: "{{ login_name }}"
+ username: "{{ username }}"
+ default_schema: "{{ default_schema }}"
+ state: present
+ tags: ["sqlserver.user"]
+ block:
+ - name: Create login
+ lowlydba.sqlserver.login:
+ register: result
+ - assert:
+ that:
+ - result.data != None
+
+ - name: Create user
+ lowlydba.sqlserver.user:
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.Database == "{{ database }}"
+ - result.data.DefaultSchema == "{{ default_schema }}"
+ - result.data.Login == "{{ login_name }}"
+ - result.data.Name == "{{ username }}"
+
+ - name: Modify user's schema
+ lowlydba.sqlserver.user:
+ default_schema: "INFORMATION_SCHEMA"
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.Database == "{{ database }}"
+ - result.data.DefaultSchema == "INFORMATION_SCHEMA"
+ - result.data.Login == "{{ login_name }}"
+ - result.data.Name == "{{ username }}"
+
+ - name: Drop a user
+ lowlydba.sqlserver.user:
+ state: "absent"
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result is changed
+
+ - name: Create user in checkmode
+ lowlydba.sqlserver.user:
+ register: result
+ check_mode: true
+ - assert:
+ that:
+ - result is changed
+
+ - name: Verify checkmode works
+ lowlydba.sqlserver.user:
+ register: result
+ - assert:
+ that:
+ - result.data != None
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.Database == "{{ database }}"
+ - result.data.DefaultSchema == "{{ default_schema }}"
+ - result.data.Login == "{{ login_name }}"
+ - result.data.Name == "{{ username }}"
+
+ always:
+ - name: Drop user
+ lowlydba.sqlserver.user:
+ state: "absent"
+ - name: Drop login
+ lowlydba.sqlserver.login:
+ state: "absent"
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_listener/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_listener/aliases
new file mode 100644
index 00000000..53361dd9
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_listener/aliases
@@ -0,0 +1,4 @@
+windows/all
+windows/group/1
+context/target
+setup/once/setup_win_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_listener/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_listener/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_listener/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_listener/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_listener/tasks/main.yml
new file mode 100644
index 00000000..b4142a48
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_listener/tasks/main.yml
@@ -0,0 +1,90 @@
+---
+- name: Var block
+ vars:
+ ag_name: "ShhListen"
+ listener_name: "CanYouHear"
+ cluster_type: "None"
+ failover_mode: "Manual"
+ availability_mode: "AsynchronousCommit"
+ force: true
+ port: 1433
+ module_defaults:
+ lowlydba.sqlserver.availability_group:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ ag_name: "{{ ag_name }}"
+ cluster_type: "{{ cluster_type }}"
+ failover_mode: "{{ failover_mode }}"
+ availability_mode: "{{ availability_mode }}"
+ force: "{{ force }}"
+ lowlydba.sqlserver.ag_listener:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ ag_name: "{{ ag_name }}"
+ listener_name: "{{ listener_name }}"
+ port: "{{ port }}"
+ ip_address: "192.168.6.9"
+ subnet_mask: "255.255.255.0"
+ tags: ["ag_listener"]
+ block:
+ - name: Enable hadr
+ lowlydba.sqlserver.hadr:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ enabled: true
+ force: true
+
+ - name: Create availability group
+ lowlydba.sqlserver.availability_group:
+
+ - name: Create ag listener
+ lowlydba.sqlserver.ag_listener:
+ register: result
+ - assert:
+ that:
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.AvailabilityGroup == ag_name
+ - result.data.Name == listener_name
+ - result.data.PortNumber == port
+ - result is changed
+
+ - name: Change ag listener port
+ lowlydba.sqlserver.ag_listener:
+ port: 1434
+ register: result
+ - assert:
+ that:
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.AvailabilityGroup == ag_name
+ - result.data.Name == listener_name
+ - result.data.PortNumber == 1434
+ - result is changed
+
+ - name: Drop ag listener in checkmode
+ lowlydba.sqlserver.ag_listener:
+ state: absent
+ check_mode: true
+ register: result
+ - assert:
+ that:
+ - result is changed
+
+ - name: Drop ag listener
+ lowlydba.sqlserver.ag_listener:
+ state: absent
+ register: result
+ - assert:
+ that:
+ - result is changed
+
+ always:
+ - name: Drop availability group
+ lowlydba.sqlserver.availability_group:
+ state: absent
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_replica/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_replica/aliases
new file mode 100644
index 00000000..681188f5
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_replica/aliases
@@ -0,0 +1,4 @@
+windows/all
+windows/group/2
+context/target
+setup/once/setup_win_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_replica/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_replica/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_replica/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_replica/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_replica/tasks/main.yml
new file mode 100644
index 00000000..8a899078
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_ag_replica/tasks/main.yml
@@ -0,0 +1,81 @@
+---
+- name: Var block
+ vars:
+ ag_name: "IntAG3"
+ cluster_type: "None"
+ failover_mode: "Manual"
+ dtc_support_enabled: false
+ availability_mode: "AsynchronousCommit"
+ seeding_mode: "Manual"
+ database_health_trigger: true
+ use_last_backup: true
+ healthcheck_timeout: 15000
+ basic_availability_group: false
+ force: true
+ failure_condition_level: "OnServerDown"
+ session_timeout: 15
+ module_defaults:
+ lowlydba.sqlserver.availability_group:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ ag_name: "{{ ag_name }}"
+ cluster_type: "{{ cluster_type }}"
+ failover_mode: "{{ failover_mode }}"
+ dtc_support_enabled: "{{ dtc_support_enabled }}"
+ availability_mode: "{{ availability_mode }}"
+ seeding_mode: "{{ seeding_mode }}"
+ database_health_trigger: "{{ database_health_trigger }}"
+ use_last_backup: "{{ use_last_backup }}"
+ healthcheck_timeout: "{{ healthcheck_timeout }}"
+ basic_availability_group: "{{ basic_availability_group }}"
+ force: "{{ force }}"
+ failure_condition_level: "{{ failure_condition_level }}"
+ lowlydba.sqlserver.ag_replica:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ sql_instance_replica: "{{ sqlserver_instance }}"
+ sql_username_replica: "{{ sqlserver_username }}"
+ sql_password_replica: "{{ sqlserver_password }}"
+ ag_name: "{{ ag_name }}"
+ session_timeout: "{{ session_timeout }}"
+ configure_xe_session: true
+
+ tags: ["ag_replica"]
+ block:
+ - name: Enable hadr
+ lowlydba.sqlserver.hadr:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ enabled: true
+ force: true
+
+ - name: Create availability group
+ lowlydba.sqlserver.availability_group:
+ register: ag
+
+ - name: Set replica
+ lowlydba.sqlserver.ag_replica:
+ session_timeout: 20
+ endpoint_url: "TCP://{{ ag.data.SqlInstance }}:5022"
+ read_only_routing_list: "{{ ag.data.SqlInstance }}"
+ read_only_routing_connection_url: "TCP://{{ ag.data.SqlInstance }}:1433"
+ register: result
+ - assert:
+ that:
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.AvailabilityGroup == ag_name
+ - result.data.AvailabilityMode == availability_mode
+ - result.data.SessionTimeout == 20
+ - result.data.FailoverMode == failover_mode
+ - result.data.EndpointUrl == "TCP://{{ ag.data.SqlInstance }}:5022"
+ - result is changed
+
+ always:
+ - name: Drop availability group
+ lowlydba.sqlserver.availability_group:
+ state: absent
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job/aliases
new file mode 100644
index 00000000..113cf850
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/1
+context/target
+setup/once/setup_win_sqlserver
+needs/target/agent_job
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job/meta/main.yml
new file mode 100644
index 00000000..903427ad
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - agent_job
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_category/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_category/aliases
new file mode 100644
index 00000000..0657417f
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_category/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/1
+context/target
+setup/once/setup_win_sqlserver
+needs/target/agent_job_category
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_category/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_category/meta/main.yml
new file mode 100644
index 00000000..042a349f
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_category/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - agent_job_category
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_schedule/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_schedule/aliases
new file mode 100644
index 00000000..6edbc658
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_schedule/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/1
+context/target
+setup/once/setup_win_sqlserver
+needs/target/agent_job_schedule
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_schedule/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_schedule/meta/main.yml
new file mode 100644
index 00000000..1d28c1c8
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_schedule/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - agent_job_schedule
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_step/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_step/aliases
new file mode 100644
index 00000000..8b8c7ccd
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_step/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/1
+context/target
+setup/once/setup_win_sqlserver
+needs/target/agent_job_step
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_step/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_step/meta/main.yml
new file mode 100644
index 00000000..1f8bdef0
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_agent_job_step/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - agent_job_step
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_availability_group/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_availability_group/aliases
new file mode 100644
index 00000000..681188f5
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_availability_group/aliases
@@ -0,0 +1,4 @@
+windows/all
+windows/group/2
+context/target
+setup/once/setup_win_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_availability_group/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_availability_group/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_availability_group/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_availability_group/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_availability_group/tasks/main.yml
new file mode 100644
index 00000000..cfa1ad7e
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_availability_group/tasks/main.yml
@@ -0,0 +1,109 @@
+---
+- name: Var block
+ vars:
+ ag_name: "IntAG1"
+ cluster_type: "None"
+ failover_mode: "Manual"
+ dtc_support_enabled: false
+ availability_mode: "AsynchronousCommit"
+ seeding_mode: "Automatic"
+ database_health_trigger: true
+ use_last_backup: false
+ healthcheck_timeout: 15000
+ basic_availability_group: false
+ force: false
+ failure_condition_level: "OnServerDown"
+ database: "ag-test-db"
+ module_defaults:
+ lowlydba.sqlserver.availability_group:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ ag_name: "{{ ag_name }}"
+ cluster_type: "{{ cluster_type }}"
+ failover_mode: "{{ failover_mode }}"
+ dtc_support_enabled: "{{ dtc_support_enabled }}"
+ availability_mode: "{{ availability_mode }}"
+ seeding_mode: "{{ seeding_mode }}"
+ database_health_trigger: "{{ database_health_trigger }}"
+ database: "{{ database }}"
+ healthcheck_timeout: "{{ healthcheck_timeout }}"
+ basic_availability_group: "{{ basic_availability_group }}"
+ force: "{{ force }}"
+ allow_null_backup: true
+ failure_condition_level: "{{ failure_condition_level }}"
+ lowlydba.sqlserver.database:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ tags: ["availability_group"]
+ block:
+ - name: Enable hadr
+ lowlydba.sqlserver.hadr:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ enabled: true
+ force: true
+
+ - name: Create a database
+ lowlydba.sqlserver.database:
+ database: "{{ database }}"
+
+ - name: Create availability group
+ lowlydba.sqlserver.availability_group:
+ register: result
+ - assert:
+ that:
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.AvailabilityGroup == "{{ ag_name }}"
+ - result.data.ClusterType == "{{ cluster_type }}"
+ - result.data.DtcSupportEnabled is false
+ - result.data.AvailabilityReplicas != None
+ - result is changed
+
+ - name: Change availability group
+ lowlydba.sqlserver.availability_group:
+ dtc_support_enabled: true
+ all_ags: true
+ failure_condition_level: "OnServerUnresponsive"
+ register: result
+ - assert:
+ that:
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.AvailabilityGroup == "{{ ag_name }}"
+ - result.data.ClusterType == "{{ cluster_type }}"
+ - result.data.DtcSupportEnabled is true
+ - result.data.AvailabilityReplicas != None
+ - result is changed
+
+ - name: Drop availability group in check mode
+ lowlydba.sqlserver.availability_group:
+ state: absent
+ check_mode: true
+ register: result
+ - assert:
+ that:
+ - result is changed
+
+ - name: Drop availability group
+ lowlydba.sqlserver.availability_group:
+ state: absent
+ register: result
+ - assert:
+ that:
+ - result is changed
+
+ always:
+ - name: Drop availability group
+ lowlydba.sqlserver.availability_group:
+ state: absent
+
+ - name: Drop database
+ lowlydba.sqlserver.database:
+ database: "{{ database }}"
+ state: absent
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_backup/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_backup/aliases
new file mode 100644
index 00000000..f31c1291
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_backup/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/2
+context/target
+setup/once/setup_win_sqlserver
+needs/target/backup
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_backup/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_backup/meta/main.yml
new file mode 100644
index 00000000..caa9577e
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_backup/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - backup
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_credential/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_credential/aliases
new file mode 100644
index 00000000..d92135b7
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_credential/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/1
+context/target
+setup/once/setup_win_sqlserver
+needs/target/credential
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_credential/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_credential/meta/main.yml
new file mode 100644
index 00000000..49316406
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_credential/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - credential
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_database/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_database/aliases
new file mode 100644
index 00000000..d65b6b5f
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_database/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/1
+context/target
+setup/once/setup_win_sqlserver
+needs/target/database
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_database/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_database/meta/main.yml
new file mode 100644
index 00000000..e2ce31a8
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_database/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - database
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_dba_multitool/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_dba_multitool/aliases
new file mode 100644
index 00000000..5d4e3ade
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_dba_multitool/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/2
+context/target
+setup/once/setup_win_sqlserver
+needs/target/dba_multitool
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_dba_multitool/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_dba_multitool/meta/main.yml
new file mode 100644
index 00000000..9db0dd08
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_dba_multitool/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - dba_multitool
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_first_responder_kit/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_first_responder_kit/aliases
new file mode 100644
index 00000000..db95f221
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_first_responder_kit/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/2
+context/target
+setup/once/setup_win_sqlserver
+needs/target/first_responder_kit
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_first_responder_kit/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_first_responder_kit/meta/main.yml
new file mode 100644
index 00000000..26403983
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_first_responder_kit/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - first_responder_kit
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_hadr/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_hadr/aliases
new file mode 100644
index 00000000..53361dd9
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_hadr/aliases
@@ -0,0 +1,4 @@
+windows/all
+windows/group/1
+context/target
+setup/once/setup_win_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_hadr/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_hadr/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_hadr/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_hadr/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_hadr/tasks/main.yml
new file mode 100644
index 00000000..b15234a7
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_hadr/tasks/main.yml
@@ -0,0 +1,53 @@
+---
+- name: Var block
+ module_defaults:
+ lowlydba.sqlserver.hadr:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ force: true
+ tags: ["hadr"]
+ block:
+ - name: Enable hadr
+ lowlydba.sqlserver.hadr:
+ enabled: true
+ register: result
+ - assert:
+ that:
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.IsHadrEnabled is true
+
+ - name: Disable hadr
+ lowlydba.sqlserver.hadr:
+ enabled: false
+ register: result
+ - assert:
+ that:
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.IsHadrEnabled is false
+ - result is changed
+
+ - name: Checkmode enable hadr
+ lowlydba.sqlserver.hadr:
+ enabled: true
+ check_mode: true
+ register: result
+ - assert:
+ that:
+ - result is changed
+
+ - name: Verify checkmode works
+ lowlydba.sqlserver.hadr:
+ enabled: false
+ register: result
+ - assert:
+ that:
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result.data.IsHadrEnabled is false
+ - result is not changed
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_install_script/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_install_script/aliases
new file mode 100644
index 00000000..53361dd9
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_install_script/aliases
@@ -0,0 +1,4 @@
+windows/all
+windows/group/1
+context/target
+setup/once/setup_win_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_install_script/files/1-select-choice.sql b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_install_script/files/1-select-choice.sql
new file mode 100644
index 00000000..54a481ac
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_install_script/files/1-select-choice.sql
@@ -0,0 +1 @@
+SELECT 'choice';
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_install_script/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_install_script/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_install_script/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_install_script/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_install_script/tasks/main.yml
new file mode 100644
index 00000000..7892cabf
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_install_script/tasks/main.yml
@@ -0,0 +1,72 @@
+---
+- name: Var block
+ vars:
+ path: "{{ role_path }}/files/"
+ dest: "tmp/"
+ database_name: "lowlydba-migration-test"
+ module_defaults:
+ lowlydba.sqlserver.install_script:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ create_database: true
+ no_recurse: true
+ database: "{{ database_name }}"
+ #path: "{{ path }}"
+ schema_version_table: "SchemaVersion"
+ output_file: "output.txt"
+ tags: ["sqlserver.install_script"]
+
+ block:
+ - name: Copy migration script(s)
+ ansible.windows.win_copy:
+ src: "{{ path }}"
+ dest: "{{ dest }}"
+ register: script_file
+
+ - name: Install a script in no log mode
+ lowlydba.sqlserver.install_script:
+ no_log_version: true
+ path: "{{ script_file.dest }}"
+ register: result
+ - assert:
+ that:
+ - result.data.Database == "{{ database_name }}"
+ - result.data.Successful == true
+ - result is changed
+
+ - name: Install a script in checkmode
+ lowlydba.sqlserver.install_script:
+ path: "{{ script_file.dest }}"
+ register: result
+ check_mode: true
+ - assert:
+ that:
+ - result is changed
+
+ - name: Install a script
+ lowlydba.sqlserver.install_script:
+ path: "{{ script_file.dest }}"
+ register: result
+ - assert:
+ that:
+ - result.data.Database == "{{ database_name }}"
+ - result.data.Successful == true
+ - result is changed
+
+ - name: Install same script
+ lowlydba.sqlserver.install_script:
+ path: "{{ script_file.dest }}"
+ register: result
+ - assert:
+ that:
+ - result is not changed
+
+ always:
+ - name: Cleanup database
+ lowlydba.sqlserver.database:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ database: "{{ database_name }}"
+ state: absent
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_instance_info/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_instance_info/aliases
new file mode 100644
index 00000000..0340b3e2
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_instance_info/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/2
+context/target
+setup/once/setup_win_sqlserver
+needs/target/instance_info
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_instance_info/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_instance_info/meta/main.yml
new file mode 100644
index 00000000..abcbf15e
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_instance_info/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - instance_info
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_login/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_login/aliases
new file mode 100644
index 00000000..391c2f48
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_login/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/2
+context/target
+setup/once/setup_win_sqlserver
+needs/target/login
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_login/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_login/meta/main.yml
new file mode 100644
index 00000000..a74c1d5b
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_login/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - login
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_maintenance_solution/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_maintenance_solution/aliases
new file mode 100644
index 00000000..a5d87690
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_maintenance_solution/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/2
+context/target
+setup/once/setup_win_sqlserver
+needs/target/maintenance_solution
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_maintenance_solution/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_maintenance_solution/meta/main.yml
new file mode 100644
index 00000000..7db45301
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_maintenance_solution/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - maintenance_solution
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_memory/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_memory/aliases
new file mode 100644
index 00000000..74d59ced
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_memory/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/2
+context/target
+setup/once/setup_win_sqlserver
+needs/target/memory
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_memory/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_memory/meta/main.yml
new file mode 100644
index 00000000..3b6d1394
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_memory/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - memory
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_nonquery/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_nonquery/aliases
new file mode 100644
index 00000000..bdd24729
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_nonquery/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/2
+context/target
+setup/once/setup_win_sqlserver
+needs/target/nonquery
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_nonquery/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_nonquery/meta/main.yml
new file mode 100644
index 00000000..42a2f3dc
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_nonquery/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - nonquery
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_resource_governor/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_resource_governor/aliases
new file mode 100644
index 00000000..954bb313
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_resource_governor/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/2
+context/target
+setup/once/setup_win_sqlserver
+needs/target/resource_governor
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_resource_governor/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_resource_governor/meta/main.yml
new file mode 100644
index 00000000..eb714ab2
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_resource_governor/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - resource_governor
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_restore/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_restore/aliases
new file mode 100644
index 00000000..681188f5
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_restore/aliases
@@ -0,0 +1,4 @@
+windows/all
+windows/group/2
+context/target
+setup/once/setup_win_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_restore/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_restore/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_restore/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_restore/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_restore/tasks/main.yml
new file mode 100644
index 00000000..59c80e62
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_restore/tasks/main.yml
@@ -0,0 +1,31 @@
+---
+- name: Var block
+ vars:
+ database_name: "model"
+ restore_database: "model_restore"
+ tags: ["restore"]
+ block:
+ - name: Backup a database
+ lowlydba.sqlserver.backup:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ database: "{{ database_name }}"
+ register: backup_result
+
+ - name: Restore a database
+ lowlydba.sqlserver.restore:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ database: "{{ restore_database }}"
+ path: "{{ backup_result.data.BackupPath }}"
+ replace_db_name_in_file: true
+ block_size: "16kb"
+ destination_file_suffix: "_new"
+ destination_file_prefix: "db_"
+ register: result
+ - assert:
+ that:
+ - result.data.SqlInstance != None
+ - result.data.Database == restore_database
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_rg_resource_pool/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_rg_resource_pool/aliases
new file mode 100644
index 00000000..b942bc24
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_rg_resource_pool/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/2
+context/target
+setup/once/setup_win_sqlserver
+needs/target/rg_resource_pool
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_rg_resource_pool/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_rg_resource_pool/meta/main.yml
new file mode 100644
index 00000000..66db486b
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_rg_resource_pool/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - rg_resource_pool
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_rg_workload_group/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_rg_workload_group/aliases
new file mode 100644
index 00000000..6cf3ad27
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_rg_workload_group/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/2
+context/target
+setup/once/setup_win_sqlserver
+needs/target/rg_workload_group
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_rg_workload_group/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_rg_workload_group/meta/main.yml
new file mode 100644
index 00000000..e556da43
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_rg_workload_group/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - rg_workload_group
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sa/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sa/aliases
new file mode 100644
index 00000000..0dae9bec
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sa/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/1
+context/target
+setup/once/setup_win_sqlserver
+needs/target/sa
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sa/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sa/meta/main.yml
new file mode 100644
index 00000000..4183f797
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sa/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - sa
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sp_configure/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sp_configure/aliases
new file mode 100644
index 00000000..e229d3e8
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sp_configure/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/1
+context/target
+setup/once/setup_win_sqlserver
+needs/target/sp_configure
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sp_configure/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sp_configure/meta/main.yml
new file mode 100644
index 00000000..c5d8f4ae
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sp_configure/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - sp_configure
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sp_whoisactive/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sp_whoisactive/aliases
new file mode 100644
index 00000000..24417788
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sp_whoisactive/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/1
+context/target
+setup/once/setup_win_sqlserver
+needs/target/sp_whoisactive
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sp_whoisactive/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sp_whoisactive/meta/main.yml
new file mode 100644
index 00000000..b356fa02
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_sp_whoisactive/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - sp_whoisactive
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_spn/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_spn/aliases
new file mode 100644
index 00000000..681188f5
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_spn/aliases
@@ -0,0 +1,4 @@
+windows/all
+windows/group/2
+context/target
+setup/once/setup_win_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_spn/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_spn/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_spn/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_spn/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_spn/tasks/main.yml
new file mode 100644
index 00000000..85123a5b
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_spn/tasks/main.yml
@@ -0,0 +1,38 @@
+---
+- name: Var block
+ vars:
+ service_account: "NT SERVICE\\MSSQLSERVER"
+ property: "msDS-AllowedToDelegateTo"
+ module_defaults:
+ lowlydba.sqlserver.spn:
+ computer: "{{ sqlserver_instance }}"
+ service_account: "{{ service_account }}"
+ tags: ["spn"]
+ block:
+ # Super limited testing below; no AD available on Github runners
+ # and with how simple this module is, its not worth the cost
+ # to implement a robust solution.
+ # - name: Set SPN
+ # lowlydba.sqlserver.spn:
+ # register: result
+ # - assert:
+ # that:
+ # - result.data.Name != None
+ # - result.data.ServiceAccount == service_account
+ # - result.data.Property == property
+ # - result.data.IsSet is true
+ # - result.data.Notes == "Successfully added SPN"
+ # - result is changed
+
+ - name: Remove spn
+ lowlydba.sqlserver.spn:
+ state: "absent"
+ register: result
+ - assert:
+ that:
+ # - result.data.Name != None
+ # - result.data.ServiceAccount == service_account
+ # - result.data.Property == property
+ # - result.data.IsSet is false
+ # - result.data.Notes == "Successfully removed SPN"
+ - result is not changed
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_tcp_port/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_tcp_port/aliases
new file mode 100644
index 00000000..53361dd9
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_tcp_port/aliases
@@ -0,0 +1,4 @@
+windows/all
+windows/group/1
+context/target
+setup/once/setup_win_sqlserver
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_tcp_port/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_tcp_port/meta/main.yml
new file mode 100644
index 00000000..a3309752
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_tcp_port/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_sqlserver_test_plugins
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_tcp_port/tasks/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_tcp_port/tasks/main.yml
new file mode 100644
index 00000000..d118cb95
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_tcp_port/tasks/main.yml
@@ -0,0 +1,49 @@
+---
+- name: Var block
+ module_defaults:
+ lowlydba.sqlserver.tcp_port:
+
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ tags: ["tcp_port"]
+ block:
+
+ - name: Get instance info
+ lowlydba.sqlserver.instance_info:
+ sql_instance: "{{ sqlserver_instance }}"
+ sql_username: "{{ sqlserver_username }}"
+ sql_password: "{{ sqlserver_password }}"
+ register: instance
+
+ - name: Set default
+ lowlydba.sqlserver.tcp_port:
+ sql_instance: "{{ instance.data.SqlInstance }}"
+ port: 1433
+ register: result
+ - assert:
+ that:
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+
+ - name: Checkmode
+ lowlydba.sqlserver.tcp_port:
+ sql_instance: "{{ instance.data.SqlInstance }}"
+ port: 1434
+ check_mode: true
+ register: result
+ - assert:
+ that:
+ - result is changed
+
+ - name: Verify checkmode works
+ lowlydba.sqlserver.tcp_port:
+ sql_instance: "{{ instance.data.SqlInstance }}"
+ port: 1433
+ register: result
+ - assert:
+ that:
+ - result.data.ComputerName != None
+ - result.data.InstanceName != None
+ - result.data.SqlInstance != None
+ - result is not changed
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_traceflag/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_traceflag/aliases
new file mode 100644
index 00000000..37ace9c7
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_traceflag/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/2
+context/target
+setup/once/setup_win_sqlserver
+needs/target/traceflag
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_traceflag/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_traceflag/meta/main.yml
new file mode 100644
index 00000000..e9b7437d
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_traceflag/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - traceflag
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_user/aliases b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_user/aliases
new file mode 100644
index 00000000..35c77c97
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_user/aliases
@@ -0,0 +1,5 @@
+windows/all
+windows/group/1
+context/target
+setup/once/setup_win_sqlserver
+needs/target/user
diff --git a/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_user/meta/main.yml b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_user/meta/main.yml
new file mode 100644
index 00000000..e9806117
--- /dev/null
+++ b/ansible_collections/lowlydba/sqlserver/tests/integration/targets/win_user/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - user