diff options
Diffstat (limited to 'ansible_collections/lowlydba/sqlserver/tests')
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 |