diff --git a/.github/workflows/ansible-test-windows.yml b/.github/workflows/ansible-test-windows.yml index 34b12011..f45275f4 100644 --- a/.github/workflows/ansible-test-windows.yml +++ b/.github/workflows/ansible-test-windows.yml @@ -121,11 +121,21 @@ jobs: update: "false" use-cache: "true" wsl-version: 1 - additional-packages: | - git - ${{ matrix.python }} - python3-pip - openssh-client + + # archive.ubuntu.com/security.ubuntu.com occasionally serve a Packages file that doesn't + # match the just-fetched InRelease hash (mirror mid-sync). Retrying almost always succeeds, + # so do our own apt-get here instead of relying on setup-wsl's one-shot additional-packages. + - name: Install WSL packages (with retry) # zizmor: ignore[template-injection] -- matrix.python is a controlled enum value (python3) + run: | + echo 'Acquire::Retries "3";' | sudo tee /etc/apt/apt.conf.d/80-retries > /dev/null + for attempt in 1 2 3; do + if sudo apt-get update && sudo apt-get install -y git ${{ matrix.python }} python3-pip openssh-client; then + exit 0 + fi + echo "apt-get failed (attempt $attempt), retrying..." + sleep 10 + done + exit 1 - name: Get Linux workspace path shell: pwsh diff --git a/README.md b/README.md index 5ebcc7b4..adcab67b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# lowlydba.sqlserver Collection for Ansible - +![lowlydba.sqlserver](docs/images/hero.png) +# lowlydba.sqlserver Collection for Ansible [![CI](https://github.com/lowlydba/lowlydba.sqlserver/actions/workflows/ansible-test.yml/badge.svg)](https://github.com/lowlydba/lowlydba.sqlserver/actions/workflows/ansible-test.yml) [![CI (Windows)](https://github.com/lowlydba/lowlydba.sqlserver/actions/workflows/ansible-test-windows.yml/badge.svg)](https://github.com/lowlydba/lowlydba.sqlserver/actions/workflows/ansible-test-windows.yml) [![codecov](https://codecov.io/gh/lowlydba/lowlydba.sqlserver/branch/main/graph/badge.svg?token=3TW3VBCn9N)](https://codecov.io/gh/lowlydba/lowlydba.sqlserver) diff --git a/changelogs/fragments/381-integration-test-gaps.yml b/changelogs/fragments/381-integration-test-gaps.yml new file mode 100644 index 00000000..a09cbfe5 --- /dev/null +++ b/changelogs/fragments/381-integration-test-gaps.yml @@ -0,0 +1,5 @@ +bugfixes: + - backup, restore - Fix ``changed`` being reported as ``false`` in ``check_mode``, since ``Backup-DbaDatabase``/``Restore-DbaDatabase`` return no output under ``-WhatIf`` (https://github.com/lowlydba/lowlydba.sqlserver/issues/381). + - availability_group - Fix ``changed`` being incorrectly reported as ``true`` on an unchanged AG. ``Get-DbaAvailabilityGroup``'s SMO object never populates ``FailureConditionLevel``/``HealthCheckTimeout`` (they read back as unset defaults), and ``sys.availability_groups`` is a cache of the WSFC cluster resource's copy, so it's empty for AGs with ``cluster_type`` set to ``None``. Since neither source can be trusted, both properties are now excluded from the idempotency diff; they're still applied via ``Set-DbaAvailabilityGroup`` whenever another property change triggers an update (https://github.com/lowlydba/lowlydba.sqlserver/issues/381). +minor_changes: + - Add check-mode assertions for the ``backup`` and ``restore`` integration test targets, add idempotency assertions for the ``availability_group`` and ``ag_replica`` targets, and exercise a non-default ``deployment_method`` in the ``install_script`` targets (https://github.com/lowlydba/lowlydba.sqlserver/issues/381). diff --git a/docs/images/hero.png b/docs/images/hero.png new file mode 100644 index 00000000..93e02d9e Binary files /dev/null and b/docs/images/hero.png differ diff --git a/docs/images/hero.svg b/docs/images/hero.svg new file mode 100644 index 00000000..023031a9 --- /dev/null +++ b/docs/images/hero.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + lowlydba.sqlserver + + + + + Ansible collection for SQL Server management, powered by dbatools + + + + \ No newline at end of file diff --git a/plugins/modules/availability_group.ps1 b/plugins/modules/availability_group.ps1 index 911ea0fc..0a004965 100644 --- a/plugins/modules/availability_group.ps1 +++ b/plugins/modules/availability_group.ps1 @@ -210,7 +210,13 @@ try { if ($null -ne $isDistributedAg) { $setAgSplat.Add("IsDistributedAvailabilityGroup", [bool]$isDistributedAg) } - $compareProperty = $setAgSplat.Keys | Where-Object { $_ -ne "AllAvailabilityGroups" } + # FailureConditionLevel/HealthCheckTimeout are excluded from the diff: Get-DbaAvailabilityGroup's + # SMO object never populates them (they read back as unset defaults), and sys.availability_groups + # is just a cache of the WSFC cluster resource's copy, so it's empty for ClusterType None AGs + # (no cluster resource to cache from). There's no reliable way to read the real value back, so + # drift on these two can't be detected. They're still applied via Set-DbaAvailabilityGroup below + # whenever another property change triggers an update. + $compareProperty = $setAgSplat.Keys | Where-Object { $_ -notin @("AllAvailabilityGroups", "FailureConditionLevel", "HealthCheckTimeout") } $agDiff = Get-DesiredStateDiff -Current $existingAG -Desired $setAgSplat -Property $compareProperty if ($agDiff.Count -gt 0) { $output = $existingAG | Set-DbaAvailabilityGroup @setAgSplat diff --git a/plugins/modules/backup.ps1 b/plugins/modules/backup.ps1 index f46b4906..cf20b0df 100644 --- a/plugins/modules/backup.ps1 +++ b/plugins/modules/backup.ps1 @@ -129,6 +129,10 @@ try { if ($null -ne $output) { $resultData = ConvertTo-SerializableObject -InputObject $output $module.Result.data = $resultData + } + # Backup-DbaDatabase returns nothing under -WhatIf, but a backup is never a no-op, so report + # changed based on check_mode too, not just on whether an output object came back. + if ($checkMode -or $null -ne $output) { $module.Result.changed = $true } $module.ExitJson() diff --git a/plugins/modules/restore.ps1 b/plugins/modules/restore.ps1 index 811b3272..4e040394 100644 --- a/plugins/modules/restore.ps1 +++ b/plugins/modules/restore.ps1 @@ -161,6 +161,10 @@ try { if ($null -ne $output) { $resultData = ConvertTo-SerializableObject -InputObject $output $module.Result.data = $resultData + } + # Restore-DbaDatabase returns nothing under -WhatIf, but a restore is never a no-op, so report + # changed based on check_mode too, not just on whether an output object came back. + if ($checkMode -or $null -ne $output) { $module.Result.changed = $true } $module.ExitJson() diff --git a/tests/integration/targets/backup/tasks/main.yml b/tests/integration/targets/backup/tasks/main.yml index 98a1ace9..0b31e48d 100644 --- a/tests/integration/targets/backup/tasks/main.yml +++ b/tests/integration/targets/backup/tasks/main.yml @@ -23,3 +23,16 @@ - result.data.End != None - result.data.Duration != None - result is changed + + - name: Backup a database in check mode + lowlydba.sqlserver.backup: + sql_instance: "{{ sqlserver_instance }}" + sql_username: "{{ sqlserver_username }}" + sql_password: "{{ sqlserver_password }}" + database: "{{ database_name }}" + block_size: "16kb" + check_mode: true + register: result + - assert: + that: + - result is changed diff --git a/tests/integration/targets/install_script/tasks/main.yml b/tests/integration/targets/install_script/tasks/main.yml index d26599c8..11cddf0b 100644 --- a/tests/integration/targets/install_script/tasks/main.yml +++ b/tests/integration/targets/install_script/tasks/main.yml @@ -20,6 +20,7 @@ - name: Install a script in no log mode lowlydba.sqlserver.install_script: no_log_version: true + deployment_method: "SingleTransaction" register: result - assert: that: diff --git a/tests/integration/targets/win_ag_listener/tasks/main.yml b/tests/integration/targets/win_ag_listener/tasks/main.yml index 68604997..570b97ca 100644 --- a/tests/integration/targets/win_ag_listener/tasks/main.yml +++ b/tests/integration/targets/win_ag_listener/tasks/main.yml @@ -7,7 +7,7 @@ failover_mode: "Manual" availability_mode: "AsynchronousCommit" force: true - port: 1433 + listener_port: 1433 module_defaults: lowlydba.sqlserver.availability_group: sql_instance: "{{ sqlserver_instance }}" @@ -24,7 +24,7 @@ sql_password: "{{ sqlserver_password }}" ag_name: "{{ ag_name }}" listener_name: "{{ listener_name }}" - port: "{{ port }}" + port: "{{ listener_port }}" ip_address: - "192.168.6.9" subnet_mask: @@ -52,7 +52,7 @@ - result.data.SqlInstance != None - result.data.AvailabilityGroup == ag_name - result.data.Name == listener_name - - result.data.PortNumber == port + - result.data.PortNumber == listener_port - result is changed - name: Change ag listener port diff --git a/tests/integration/targets/win_ag_replica/tasks/main.yml b/tests/integration/targets/win_ag_replica/tasks/main.yml index feb3acb5..698939a1 100644 --- a/tests/integration/targets/win_ag_replica/tasks/main.yml +++ b/tests/integration/targets/win_ag_replica/tasks/main.yml @@ -78,6 +78,17 @@ - result.data.FailoverMode == failover_mode - result is changed + - name: Set replica again (idempotency check) + 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 is not changed + always: - name: Drop availability group lowlydba.sqlserver.availability_group: diff --git a/tests/integration/targets/win_availability_group/tasks/main.yml b/tests/integration/targets/win_availability_group/tasks/main.yml index ff96d88e..677a0c40 100644 --- a/tests/integration/targets/win_availability_group/tasks/main.yml +++ b/tests/integration/targets/win_availability_group/tasks/main.yml @@ -64,6 +64,13 @@ - result.data.AvailabilityReplicas != None - result is changed + - name: Create availability group again (idempotency check) + lowlydba.sqlserver.availability_group: + register: result + - assert: + that: + - result is not changed + - name: Change availability group lowlydba.sqlserver.availability_group: dtc_support_enabled: true diff --git a/tests/integration/targets/win_install_script/tasks/main.yml b/tests/integration/targets/win_install_script/tasks/main.yml index 38a00a46..55745e8f 100644 --- a/tests/integration/targets/win_install_script/tasks/main.yml +++ b/tests/integration/targets/win_install_script/tasks/main.yml @@ -28,6 +28,7 @@ lowlydba.sqlserver.install_script: no_log_version: true path: "{{ script_file.dest }}" + deployment_method: "SingleTransaction" register: result - assert: that: diff --git a/tests/integration/targets/win_restore/tasks/main.yml b/tests/integration/targets/win_restore/tasks/main.yml index 88841633..d07d7da8 100644 --- a/tests/integration/targets/win_restore/tasks/main.yml +++ b/tests/integration/targets/win_restore/tasks/main.yml @@ -13,6 +13,23 @@ database: "{{ database_name }}" register: backup_result + - name: Restore a database in check mode + 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_" + check_mode: true + register: result + - assert: + that: + - result is changed + - name: Restore a database lowlydba.sqlserver.restore: sql_instance: "{{ sqlserver_instance }}" @@ -29,6 +46,7 @@ that: - result.data.SqlInstance != None - result.data.Database == restore_database + - result is changed - name: Test error when restoring to an existing database lowlydba.sqlserver.restore: