diff --git a/.github/workflows/test-integration-v2-TestACLAllowStarDst.yaml b/.github/workflows/test-integration-v2-TestACLAllowStarDst.yaml index 304ea365..d26137a1 100644 --- a/.github/workflows/test-integration-v2-TestACLAllowStarDst.yaml +++ b/.github/workflows/test-integration-v2-TestACLAllowStarDst.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestACLAllowStarDst: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestACLAllowStarDst if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestACLAllowUser80Dst.yaml b/.github/workflows/test-integration-v2-TestACLAllowUser80Dst.yaml index cfc58df9..0501811e 100644 --- a/.github/workflows/test-integration-v2-TestACLAllowUser80Dst.yaml +++ b/.github/workflows/test-integration-v2-TestACLAllowUser80Dst.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestACLAllowUser80Dst: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestACLAllowUser80Dst if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestACLAllowUserDst.yaml b/.github/workflows/test-integration-v2-TestACLAllowUserDst.yaml index f0ed3d4f..93ef0611 100644 --- a/.github/workflows/test-integration-v2-TestACLAllowUserDst.yaml +++ b/.github/workflows/test-integration-v2-TestACLAllowUserDst.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestACLAllowUserDst: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestACLAllowUserDst if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestACLDenyAllPort80.yaml b/.github/workflows/test-integration-v2-TestACLDenyAllPort80.yaml index 7b0a9c37..ae559845 100644 --- a/.github/workflows/test-integration-v2-TestACLDenyAllPort80.yaml +++ b/.github/workflows/test-integration-v2-TestACLDenyAllPort80.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestACLDenyAllPort80: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestACLDenyAllPort80 if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestACLDevice1CanAccessDevice2.yaml b/.github/workflows/test-integration-v2-TestACLDevice1CanAccessDevice2.yaml index 7dd443c4..7d124b3a 100644 --- a/.github/workflows/test-integration-v2-TestACLDevice1CanAccessDevice2.yaml +++ b/.github/workflows/test-integration-v2-TestACLDevice1CanAccessDevice2.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestACLDevice1CanAccessDevice2: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestACLDevice1CanAccessDevice2 if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestACLHostsInNetMapTable.yaml b/.github/workflows/test-integration-v2-TestACLHostsInNetMapTable.yaml index ab8b69b7..7d74ed0e 100644 --- a/.github/workflows/test-integration-v2-TestACLHostsInNetMapTable.yaml +++ b/.github/workflows/test-integration-v2-TestACLHostsInNetMapTable.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestACLHostsInNetMapTable: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestACLHostsInNetMapTable if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestACLNamedHostsCanReach.yaml b/.github/workflows/test-integration-v2-TestACLNamedHostsCanReach.yaml index cdaa50dc..c4d0fbde 100644 --- a/.github/workflows/test-integration-v2-TestACLNamedHostsCanReach.yaml +++ b/.github/workflows/test-integration-v2-TestACLNamedHostsCanReach.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestACLNamedHostsCanReach: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestACLNamedHostsCanReach if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestACLNamedHostsCanReachBySubnet.yaml b/.github/workflows/test-integration-v2-TestACLNamedHostsCanReachBySubnet.yaml index c3214f40..8434570e 100644 --- a/.github/workflows/test-integration-v2-TestACLNamedHostsCanReachBySubnet.yaml +++ b/.github/workflows/test-integration-v2-TestACLNamedHostsCanReachBySubnet.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestACLNamedHostsCanReachBySubnet: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestACLNamedHostsCanReachBySubnet if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestApiKeyCommand.yaml b/.github/workflows/test-integration-v2-TestApiKeyCommand.yaml index 1c526396..ef16d500 100644 --- a/.github/workflows/test-integration-v2-TestApiKeyCommand.yaml +++ b/.github/workflows/test-integration-v2-TestApiKeyCommand.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestApiKeyCommand: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestApiKeyCommand if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestAuthKeyLogoutAndRelogin.yaml b/.github/workflows/test-integration-v2-TestAuthKeyLogoutAndRelogin.yaml index f7cb31fe..e5f83edd 100644 --- a/.github/workflows/test-integration-v2-TestAuthKeyLogoutAndRelogin.yaml +++ b/.github/workflows/test-integration-v2-TestAuthKeyLogoutAndRelogin.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestAuthKeyLogoutAndRelogin: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestAuthKeyLogoutAndRelogin if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestAuthWebFlowAuthenticationPingAll.yaml b/.github/workflows/test-integration-v2-TestAuthWebFlowAuthenticationPingAll.yaml index cb4415dd..f870fae9 100644 --- a/.github/workflows/test-integration-v2-TestAuthWebFlowAuthenticationPingAll.yaml +++ b/.github/workflows/test-integration-v2-TestAuthWebFlowAuthenticationPingAll.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestAuthWebFlowAuthenticationPingAll: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestAuthWebFlowAuthenticationPingAll if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestAuthWebFlowLogoutAndRelogin.yaml b/.github/workflows/test-integration-v2-TestAuthWebFlowLogoutAndRelogin.yaml index 991b9c33..d30882b4 100644 --- a/.github/workflows/test-integration-v2-TestAuthWebFlowLogoutAndRelogin.yaml +++ b/.github/workflows/test-integration-v2-TestAuthWebFlowLogoutAndRelogin.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestAuthWebFlowLogoutAndRelogin: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestAuthWebFlowLogoutAndRelogin if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestCreateTailscale.yaml b/.github/workflows/test-integration-v2-TestCreateTailscale.yaml index d01a9d54..343337f1 100644 --- a/.github/workflows/test-integration-v2-TestCreateTailscale.yaml +++ b/.github/workflows/test-integration-v2-TestCreateTailscale.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestCreateTailscale: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestCreateTailscale if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestDERPServerScenario.yaml b/.github/workflows/test-integration-v2-TestDERPServerScenario.yaml index 06057663..3cf22384 100644 --- a/.github/workflows/test-integration-v2-TestDERPServerScenario.yaml +++ b/.github/workflows/test-integration-v2-TestDERPServerScenario.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestDERPServerScenario: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestDERPServerScenario if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestEnablingRoutes.yaml b/.github/workflows/test-integration-v2-TestEnablingRoutes.yaml index ff34f0c6..a2e70c13 100644 --- a/.github/workflows/test-integration-v2-TestEnablingRoutes.yaml +++ b/.github/workflows/test-integration-v2-TestEnablingRoutes.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestEnablingRoutes: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestEnablingRoutes if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestEphemeral.yaml b/.github/workflows/test-integration-v2-TestEphemeral.yaml index 5e046f71..fbba8282 100644 --- a/.github/workflows/test-integration-v2-TestEphemeral.yaml +++ b/.github/workflows/test-integration-v2-TestEphemeral.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestEphemeral: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestEphemeral if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestExpireNode.yaml b/.github/workflows/test-integration-v2-TestExpireNode.yaml index 699599b1..f19f9d29 100644 --- a/.github/workflows/test-integration-v2-TestExpireNode.yaml +++ b/.github/workflows/test-integration-v2-TestExpireNode.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestExpireNode: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestExpireNode if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestHeadscale.yaml b/.github/workflows/test-integration-v2-TestHeadscale.yaml index 9a73ee1b..e6ffce26 100644 --- a/.github/workflows/test-integration-v2-TestHeadscale.yaml +++ b/.github/workflows/test-integration-v2-TestHeadscale.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestHeadscale: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestHeadscale if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestNodeCommand.yaml b/.github/workflows/test-integration-v2-TestNodeCommand.yaml index 9fd84e1b..3d26a1e5 100644 --- a/.github/workflows/test-integration-v2-TestNodeCommand.yaml +++ b/.github/workflows/test-integration-v2-TestNodeCommand.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestNodeCommand: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestNodeCommand if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestNodeExpireCommand.yaml b/.github/workflows/test-integration-v2-TestNodeExpireCommand.yaml index b6ebd22a..af606770 100644 --- a/.github/workflows/test-integration-v2-TestNodeExpireCommand.yaml +++ b/.github/workflows/test-integration-v2-TestNodeExpireCommand.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestNodeExpireCommand: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestNodeExpireCommand if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestNodeMoveCommand.yaml b/.github/workflows/test-integration-v2-TestNodeMoveCommand.yaml index 29e1df91..1862e5d8 100644 --- a/.github/workflows/test-integration-v2-TestNodeMoveCommand.yaml +++ b/.github/workflows/test-integration-v2-TestNodeMoveCommand.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestNodeMoveCommand: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestNodeMoveCommand if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestNodeRenameCommand.yaml b/.github/workflows/test-integration-v2-TestNodeRenameCommand.yaml index 7acc923d..c0699485 100644 --- a/.github/workflows/test-integration-v2-TestNodeRenameCommand.yaml +++ b/.github/workflows/test-integration-v2-TestNodeRenameCommand.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestNodeRenameCommand: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestNodeRenameCommand if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestNodeTagCommand.yaml b/.github/workflows/test-integration-v2-TestNodeTagCommand.yaml index ae4f6861..2fc7cc6e 100644 --- a/.github/workflows/test-integration-v2-TestNodeTagCommand.yaml +++ b/.github/workflows/test-integration-v2-TestNodeTagCommand.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestNodeTagCommand: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestNodeTagCommand if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestOIDCAuthenticationPingAll.yaml b/.github/workflows/test-integration-v2-TestOIDCAuthenticationPingAll.yaml index 2bf77baf..f8ece125 100644 --- a/.github/workflows/test-integration-v2-TestOIDCAuthenticationPingAll.yaml +++ b/.github/workflows/test-integration-v2-TestOIDCAuthenticationPingAll.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestOIDCAuthenticationPingAll: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestOIDCAuthenticationPingAll if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestOIDCExpireNodesBasedOnTokenExpiry.yaml b/.github/workflows/test-integration-v2-TestOIDCExpireNodesBasedOnTokenExpiry.yaml index 0310c26c..32b4d307 100644 --- a/.github/workflows/test-integration-v2-TestOIDCExpireNodesBasedOnTokenExpiry.yaml +++ b/.github/workflows/test-integration-v2-TestOIDCExpireNodesBasedOnTokenExpiry.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestOIDCExpireNodesBasedOnTokenExpiry: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestOIDCExpireNodesBasedOnTokenExpiry if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestPingAllByHostname.yaml b/.github/workflows/test-integration-v2-TestPingAllByHostname.yaml index 714cc7fb..712b6f48 100644 --- a/.github/workflows/test-integration-v2-TestPingAllByHostname.yaml +++ b/.github/workflows/test-integration-v2-TestPingAllByHostname.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestPingAllByHostname: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestPingAllByHostname if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestPingAllByIP.yaml b/.github/workflows/test-integration-v2-TestPingAllByIP.yaml index c9632903..d460700f 100644 --- a/.github/workflows/test-integration-v2-TestPingAllByIP.yaml +++ b/.github/workflows/test-integration-v2-TestPingAllByIP.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestPingAllByIP: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestPingAllByIP if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestPreAuthKeyCommand.yaml b/.github/workflows/test-integration-v2-TestPreAuthKeyCommand.yaml index e3d23fba..744e68c0 100644 --- a/.github/workflows/test-integration-v2-TestPreAuthKeyCommand.yaml +++ b/.github/workflows/test-integration-v2-TestPreAuthKeyCommand.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestPreAuthKeyCommand: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestPreAuthKeyCommand if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestPreAuthKeyCommandReusableEphemeral.yaml b/.github/workflows/test-integration-v2-TestPreAuthKeyCommandReusableEphemeral.yaml index 3939b1c8..04856260 100644 --- a/.github/workflows/test-integration-v2-TestPreAuthKeyCommandReusableEphemeral.yaml +++ b/.github/workflows/test-integration-v2-TestPreAuthKeyCommandReusableEphemeral.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestPreAuthKeyCommandReusableEphemeral: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestPreAuthKeyCommandReusableEphemeral if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestPreAuthKeyCommandWithoutExpiry.yaml b/.github/workflows/test-integration-v2-TestPreAuthKeyCommandWithoutExpiry.yaml index 735e1b92..9540543d 100644 --- a/.github/workflows/test-integration-v2-TestPreAuthKeyCommandWithoutExpiry.yaml +++ b/.github/workflows/test-integration-v2-TestPreAuthKeyCommandWithoutExpiry.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestPreAuthKeyCommandWithoutExpiry: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestPreAuthKeyCommandWithoutExpiry if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestResolveMagicDNS.yaml b/.github/workflows/test-integration-v2-TestResolveMagicDNS.yaml index c3794fc9..3c28e21f 100644 --- a/.github/workflows/test-integration-v2-TestResolveMagicDNS.yaml +++ b/.github/workflows/test-integration-v2-TestResolveMagicDNS.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestResolveMagicDNS: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestResolveMagicDNS if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestSSHIsBlockedInACL.yaml b/.github/workflows/test-integration-v2-TestSSHIsBlockedInACL.yaml index 35f72443..ab4e360b 100644 --- a/.github/workflows/test-integration-v2-TestSSHIsBlockedInACL.yaml +++ b/.github/workflows/test-integration-v2-TestSSHIsBlockedInACL.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestSSHIsBlockedInACL: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestSSHIsBlockedInACL if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestSSHMultipleUsersAllToAll.yaml b/.github/workflows/test-integration-v2-TestSSHMultipleUsersAllToAll.yaml index d8132578..b934c232 100644 --- a/.github/workflows/test-integration-v2-TestSSHMultipleUsersAllToAll.yaml +++ b/.github/workflows/test-integration-v2-TestSSHMultipleUsersAllToAll.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestSSHMultipleUsersAllToAll: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestSSHMultipleUsersAllToAll if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestSSHNoSSHConfigured.yaml b/.github/workflows/test-integration-v2-TestSSHNoSSHConfigured.yaml index 0df1403e..94e1a03c 100644 --- a/.github/workflows/test-integration-v2-TestSSHNoSSHConfigured.yaml +++ b/.github/workflows/test-integration-v2-TestSSHNoSSHConfigured.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestSSHNoSSHConfigured: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestSSHNoSSHConfigured if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestSSHOneUserAllToAll.yaml b/.github/workflows/test-integration-v2-TestSSHOneUserAllToAll.yaml index 3ffd7830..37e1f973 100644 --- a/.github/workflows/test-integration-v2-TestSSHOneUserAllToAll.yaml +++ b/.github/workflows/test-integration-v2-TestSSHOneUserAllToAll.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestSSHOneUserAllToAll: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestSSHOneUserAllToAll if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestSSUserOnlyIsolation.yaml b/.github/workflows/test-integration-v2-TestSSUserOnlyIsolation.yaml index a2e3eadf..a8a34352 100644 --- a/.github/workflows/test-integration-v2-TestSSUserOnlyIsolation.yaml +++ b/.github/workflows/test-integration-v2-TestSSUserOnlyIsolation.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestSSUserOnlyIsolation: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestSSUserOnlyIsolation if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestTaildrop.yaml b/.github/workflows/test-integration-v2-TestTaildrop.yaml index dd7bb7c8..10dbec8f 100644 --- a/.github/workflows/test-integration-v2-TestTaildrop.yaml +++ b/.github/workflows/test-integration-v2-TestTaildrop.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestTaildrop: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestTaildrop if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestTailscaleNodesJoiningHeadcale.yaml b/.github/workflows/test-integration-v2-TestTailscaleNodesJoiningHeadcale.yaml index 697200e5..138e7074 100644 --- a/.github/workflows/test-integration-v2-TestTailscaleNodesJoiningHeadcale.yaml +++ b/.github/workflows/test-integration-v2-TestTailscaleNodesJoiningHeadcale.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestTailscaleNodesJoiningHeadcale: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestTailscaleNodesJoiningHeadcale if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/.github/workflows/test-integration-v2-TestUserCommand.yaml b/.github/workflows/test-integration-v2-TestUserCommand.yaml index 80aec27d..765e37db 100644 --- a/.github/workflows/test-integration-v2-TestUserCommand.yaml +++ b/.github/workflows/test-integration-v2-TestUserCommand.yaml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true jobs: - test: + TestUserCommand: runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run TestUserCommand if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/Dockerfile.tailscale b/Dockerfile.tailscale deleted file mode 100644 index adf37ed0..00000000 --- a/Dockerfile.tailscale +++ /dev/null @@ -1,16 +0,0 @@ -FROM ubuntu:22.04 - -ARG TAILSCALE_VERSION=* -ARG TAILSCALE_CHANNEL=stable - -RUN apt-get update \ - && apt-get install -y gnupg curl ssh dnsutils ca-certificates \ - && adduser --shell=/bin/bash ssh-it-user - -# Tailscale is deliberately split into a second stage so we can cash utils as a seperate layer. -RUN curl -fsSL https://pkgs.tailscale.com/${TAILSCALE_CHANNEL}/ubuntu/focal.gpg | apt-key add - \ - && curl -fsSL https://pkgs.tailscale.com/${TAILSCALE_CHANNEL}/ubuntu/focal.list | tee /etc/apt/sources.list.d/tailscale.list \ - && apt-get update \ - && apt-get install -y tailscale=${TAILSCALE_VERSION} \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* diff --git a/cmd/gh-action-integration-generator/main.go b/cmd/gh-action-integration-generator/main.go index 55d9cb50..6c2db5be 100644 --- a/cmd/gh-action-integration-generator/main.go +++ b/cmd/gh-action-integration-generator/main.go @@ -31,7 +31,7 @@ concurrency: cancel-in-progress: true jobs: - test: + {{.Name}}: runs-on: ubuntu-latest steps: @@ -55,7 +55,7 @@ jobs: integration_test/ config-example.yaml - - name: Run general integration tests + - name: Run {{.Name}} if: steps.changed-files.outputs.any_changed == 'true' run: | nix develop --command -- docker run \ diff --git a/integration/acl_test.go b/integration/acl_test.go index 654f557d..f60c3343 100644 --- a/integration/acl_test.go +++ b/integration/acl_test.go @@ -45,10 +45,14 @@ var veryLargeDestination = []string{ "208.0.0.0/4:*", } -func aclScenario(t *testing.T, policy *policy.ACLPolicy, clientsPerUser int) *Scenario { +func aclScenario( + t *testing.T, + policy *policy.ACLPolicy, + clientsPerUser int, +) *Scenario { t.Helper() scenario, err := NewScenario() - assert.NoError(t, err) + assertNoErr(t, err) spec := map[string]int{ "user1": clientsPerUser, @@ -58,22 +62,19 @@ func aclScenario(t *testing.T, policy *policy.ACLPolicy, clientsPerUser int) *Sc err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{ tsic.WithDockerEntrypoint([]string{ - "/bin/bash", + "/bin/sh", "-c", - "/bin/sleep 3 ; update-ca-certificates ; python3 -m http.server --bind :: 80 & tailscaled --tun=tsdev", + "/bin/sleep 3 ; apk add python3 curl ; update-ca-certificates ; python3 -m http.server --bind :: 80 & tailscaled --tun=tsdev", }), tsic.WithDockerWorkdir("/"), }, hsic.WithACLPolicy(policy), hsic.WithTestName("acl"), ) - assert.NoError(t, err) - - err = scenario.WaitForTailscaleSync() - assert.NoError(t, err) + assertNoErr(t, err) _, err = scenario.ListTailscaleClientsFQDNs() - assert.NoError(t, err) + assertNoErrListFQDN(t, err) return scenario } @@ -260,7 +261,7 @@ func TestACLHostsInNetMapTable(t *testing.T) { for name, testCase := range tests { t.Run(name, func(t *testing.T) { scenario, err := NewScenario() - assert.NoError(t, err) + assertNoErr(t, err) spec := testCase.users @@ -268,25 +269,23 @@ func TestACLHostsInNetMapTable(t *testing.T) { []tsic.Option{}, hsic.WithACLPolicy(&testCase.policy), ) - assert.NoError(t, err) + assertNoErr(t, err) + defer scenario.Shutdown() allClients, err := scenario.ListTailscaleClients() - assert.NoError(t, err) + assertNoErr(t, err) - err = scenario.WaitForTailscaleSync() - assert.NoError(t, err) + err = scenario.WaitForTailscaleSyncWithPeerCount(testCase.want["user1"]) + assertNoErrSync(t, err) for _, client := range allClients { status, err := client.Status() - assert.NoError(t, err) + assertNoErr(t, err) user := status.User[status.Self.UserID].LoginName assert.Equal(t, (testCase.want[user]), len(status.Peer)) } - - err = scenario.Shutdown() - assert.NoError(t, err) }) } } @@ -311,25 +310,26 @@ func TestACLAllowUser80Dst(t *testing.T) { }, 1, ) + defer scenario.Shutdown() user1Clients, err := scenario.ListTailscaleClients("user1") - assert.NoError(t, err) + assertNoErr(t, err) user2Clients, err := scenario.ListTailscaleClients("user2") - assert.NoError(t, err) + assertNoErr(t, err) // Test that user1 can visit all user2 for _, client := range user1Clients { for _, peer := range user2Clients { fqdn, err := peer.FQDN() - assert.NoError(t, err) + assertNoErr(t, err) url := fmt.Sprintf("http://%s/etc/hostname", fqdn) t.Logf("url from %s to %s", client.Hostname(), url) result, err := client.Curl(url) assert.Len(t, result, 13) - assert.NoError(t, err) + assertNoErr(t, err) } } @@ -337,7 +337,7 @@ func TestACLAllowUser80Dst(t *testing.T) { for _, client := range user2Clients { for _, peer := range user1Clients { fqdn, err := peer.FQDN() - assert.NoError(t, err) + assertNoErr(t, err) url := fmt.Sprintf("http://%s/etc/hostname", fqdn) t.Logf("url from %s to %s", client.Hostname(), url) @@ -347,9 +347,6 @@ func TestACLAllowUser80Dst(t *testing.T) { assert.Error(t, err) } } - - err = scenario.Shutdown() - assert.NoError(t, err) } func TestACLDenyAllPort80(t *testing.T) { @@ -370,12 +367,13 @@ func TestACLDenyAllPort80(t *testing.T) { }, 4, ) + defer scenario.Shutdown() allClients, err := scenario.ListTailscaleClients() - assert.NoError(t, err) + assertNoErr(t, err) allHostnames, err := scenario.ListTailscaleClientsFQDNs() - assert.NoError(t, err) + assertNoErr(t, err) for _, client := range allClients { for _, hostname := range allHostnames { @@ -393,9 +391,6 @@ func TestACLDenyAllPort80(t *testing.T) { assert.Error(t, err) } } - - err = scenario.Shutdown() - assert.NoError(t, err) } // Test to confirm that we can use user:* from one user. @@ -416,25 +411,26 @@ func TestACLAllowUserDst(t *testing.T) { }, 2, ) + defer scenario.Shutdown() user1Clients, err := scenario.ListTailscaleClients("user1") - assert.NoError(t, err) + assertNoErr(t, err) user2Clients, err := scenario.ListTailscaleClients("user2") - assert.NoError(t, err) + assertNoErr(t, err) // Test that user1 can visit all user2 for _, client := range user1Clients { for _, peer := range user2Clients { fqdn, err := peer.FQDN() - assert.NoError(t, err) + assertNoErr(t, err) url := fmt.Sprintf("http://%s/etc/hostname", fqdn) t.Logf("url from %s to %s", client.Hostname(), url) result, err := client.Curl(url) assert.Len(t, result, 13) - assert.NoError(t, err) + assertNoErr(t, err) } } @@ -442,7 +438,7 @@ func TestACLAllowUserDst(t *testing.T) { for _, client := range user2Clients { for _, peer := range user1Clients { fqdn, err := peer.FQDN() - assert.NoError(t, err) + assertNoErr(t, err) url := fmt.Sprintf("http://%s/etc/hostname", fqdn) t.Logf("url from %s to %s", client.Hostname(), url) @@ -452,9 +448,6 @@ func TestACLAllowUserDst(t *testing.T) { assert.Error(t, err) } } - - err = scenario.Shutdown() - assert.NoError(t, err) } // Test to confirm that we can use *:* from one user @@ -474,25 +467,26 @@ func TestACLAllowStarDst(t *testing.T) { }, 2, ) + defer scenario.Shutdown() user1Clients, err := scenario.ListTailscaleClients("user1") - assert.NoError(t, err) + assertNoErr(t, err) user2Clients, err := scenario.ListTailscaleClients("user2") - assert.NoError(t, err) + assertNoErr(t, err) // Test that user1 can visit all user2 for _, client := range user1Clients { for _, peer := range user2Clients { fqdn, err := peer.FQDN() - assert.NoError(t, err) + assertNoErr(t, err) url := fmt.Sprintf("http://%s/etc/hostname", fqdn) t.Logf("url from %s to %s", client.Hostname(), url) result, err := client.Curl(url) assert.Len(t, result, 13) - assert.NoError(t, err) + assertNoErr(t, err) } } @@ -500,7 +494,7 @@ func TestACLAllowStarDst(t *testing.T) { for _, client := range user2Clients { for _, peer := range user1Clients { fqdn, err := peer.FQDN() - assert.NoError(t, err) + assertNoErr(t, err) url := fmt.Sprintf("http://%s/etc/hostname", fqdn) t.Logf("url from %s to %s", client.Hostname(), url) @@ -510,9 +504,6 @@ func TestACLAllowStarDst(t *testing.T) { assert.Error(t, err) } } - - err = scenario.Shutdown() - assert.NoError(t, err) } // TestACLNamedHostsCanReachBySubnet is the same as @@ -537,25 +528,26 @@ func TestACLNamedHostsCanReachBySubnet(t *testing.T) { }, 3, ) + defer scenario.Shutdown() user1Clients, err := scenario.ListTailscaleClients("user1") - assert.NoError(t, err) + assertNoErr(t, err) user2Clients, err := scenario.ListTailscaleClients("user2") - assert.NoError(t, err) + assertNoErr(t, err) // Test that user1 can visit all user2 for _, client := range user1Clients { for _, peer := range user2Clients { fqdn, err := peer.FQDN() - assert.NoError(t, err) + assertNoErr(t, err) url := fmt.Sprintf("http://%s/etc/hostname", fqdn) t.Logf("url from %s to %s", client.Hostname(), url) result, err := client.Curl(url) assert.Len(t, result, 13) - assert.NoError(t, err) + assertNoErr(t, err) } } @@ -563,19 +555,16 @@ func TestACLNamedHostsCanReachBySubnet(t *testing.T) { for _, client := range user2Clients { for _, peer := range user1Clients { fqdn, err := peer.FQDN() - assert.NoError(t, err) + assertNoErr(t, err) url := fmt.Sprintf("http://%s/etc/hostname", fqdn) t.Logf("url from %s to %s", client.Hostname(), url) result, err := client.Curl(url) assert.Len(t, result, 13) - assert.NoError(t, err) + assertNoErr(t, err) } } - - err = scenario.Shutdown() - assert.NoError(t, err) } // This test aims to cover cases where individual hosts are allowed and denied @@ -677,16 +666,17 @@ func TestACLNamedHostsCanReach(t *testing.T) { &testCase.policy, 2, ) + defer scenario.Shutdown() // Since user/users dont matter here, we basically expect that some clients // will be assigned these ips and that we can pick them up for our own use. test1ip4 := netip.MustParseAddr("100.64.0.1") test1ip6 := netip.MustParseAddr("fd7a:115c:a1e0::1") test1, err := scenario.FindTailscaleClientByIP(test1ip6) - assert.NoError(t, err) + assertNoErr(t, err) test1fqdn, err := test1.FQDN() - assert.NoError(t, err) + assertNoErr(t, err) test1ip4URL := fmt.Sprintf("http://%s/etc/hostname", test1ip4.String()) test1ip6URL := fmt.Sprintf("http://[%s]/etc/hostname", test1ip6.String()) test1fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test1fqdn) @@ -694,10 +684,10 @@ func TestACLNamedHostsCanReach(t *testing.T) { test2ip4 := netip.MustParseAddr("100.64.0.2") test2ip6 := netip.MustParseAddr("fd7a:115c:a1e0::2") test2, err := scenario.FindTailscaleClientByIP(test2ip6) - assert.NoError(t, err) + assertNoErr(t, err) test2fqdn, err := test2.FQDN() - assert.NoError(t, err) + assertNoErr(t, err) test2ip4URL := fmt.Sprintf("http://%s/etc/hostname", test2ip4.String()) test2ip6URL := fmt.Sprintf("http://[%s]/etc/hostname", test2ip6.String()) test2fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test2fqdn) @@ -705,10 +695,10 @@ func TestACLNamedHostsCanReach(t *testing.T) { test3ip4 := netip.MustParseAddr("100.64.0.3") test3ip6 := netip.MustParseAddr("fd7a:115c:a1e0::3") test3, err := scenario.FindTailscaleClientByIP(test3ip6) - assert.NoError(t, err) + assertNoErr(t, err) test3fqdn, err := test3.FQDN() - assert.NoError(t, err) + assertNoErr(t, err) test3ip4URL := fmt.Sprintf("http://%s/etc/hostname", test3ip4.String()) test3ip6URL := fmt.Sprintf("http://[%s]/etc/hostname", test3ip6.String()) test3fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test3fqdn) @@ -723,7 +713,7 @@ func TestACLNamedHostsCanReach(t *testing.T) { test3ip4URL, result, ) - assert.NoError(t, err) + assertNoErr(t, err) result, err = test1.Curl(test3ip6URL) assert.Lenf( @@ -734,7 +724,7 @@ func TestACLNamedHostsCanReach(t *testing.T) { test3ip6URL, result, ) - assert.NoError(t, err) + assertNoErr(t, err) result, err = test1.Curl(test3fqdnURL) assert.Lenf( @@ -745,7 +735,7 @@ func TestACLNamedHostsCanReach(t *testing.T) { test3fqdnURL, result, ) - assert.NoError(t, err) + assertNoErr(t, err) // test2 can query test3 result, err = test2.Curl(test3ip4URL) @@ -757,7 +747,7 @@ func TestACLNamedHostsCanReach(t *testing.T) { test3ip4URL, result, ) - assert.NoError(t, err) + assertNoErr(t, err) result, err = test2.Curl(test3ip6URL) assert.Lenf( @@ -768,7 +758,7 @@ func TestACLNamedHostsCanReach(t *testing.T) { test3ip6URL, result, ) - assert.NoError(t, err) + assertNoErr(t, err) result, err = test2.Curl(test3fqdnURL) assert.Lenf( @@ -779,7 +769,7 @@ func TestACLNamedHostsCanReach(t *testing.T) { test3fqdnURL, result, ) - assert.NoError(t, err) + assertNoErr(t, err) // test3 cannot query test1 result, err = test3.Curl(test1ip4URL) @@ -818,7 +808,7 @@ func TestACLNamedHostsCanReach(t *testing.T) { result, ) - assert.NoError(t, err) + assertNoErr(t, err) result, err = test1.Curl(test2ip6URL) assert.Lenf( t, @@ -828,7 +818,7 @@ func TestACLNamedHostsCanReach(t *testing.T) { test2ip6URL, result, ) - assert.NoError(t, err) + assertNoErr(t, err) result, err = test1.Curl(test2fqdnURL) assert.Lenf( @@ -839,7 +829,7 @@ func TestACLNamedHostsCanReach(t *testing.T) { test2fqdnURL, result, ) - assert.NoError(t, err) + assertNoErr(t, err) // test2 cannot query test1 result, err = test2.Curl(test1ip4URL) @@ -853,9 +843,6 @@ func TestACLNamedHostsCanReach(t *testing.T) { result, err = test2.Curl(test1fqdnURL) assert.Empty(t, result) assert.Error(t, err) - - err = scenario.Shutdown() - assert.NoError(t, err) }) } } @@ -953,10 +940,10 @@ func TestACLDevice1CanAccessDevice2(t *testing.T) { test1ip6 := netip.MustParseAddr("fd7a:115c:a1e0::1") test1, err := scenario.FindTailscaleClientByIP(test1ip) assert.NotNil(t, test1) - assert.NoError(t, err) + assertNoErr(t, err) test1fqdn, err := test1.FQDN() - assert.NoError(t, err) + assertNoErr(t, err) test1ipURL := fmt.Sprintf("http://%s/etc/hostname", test1ip.String()) test1ip6URL := fmt.Sprintf("http://[%s]/etc/hostname", test1ip6.String()) test1fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test1fqdn) @@ -965,10 +952,10 @@ func TestACLDevice1CanAccessDevice2(t *testing.T) { test2ip6 := netip.MustParseAddr("fd7a:115c:a1e0::2") test2, err := scenario.FindTailscaleClientByIP(test2ip) assert.NotNil(t, test2) - assert.NoError(t, err) + assertNoErr(t, err) test2fqdn, err := test2.FQDN() - assert.NoError(t, err) + assertNoErr(t, err) test2ipURL := fmt.Sprintf("http://%s/etc/hostname", test2ip.String()) test2ip6URL := fmt.Sprintf("http://[%s]/etc/hostname", test2ip6.String()) test2fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test2fqdn) @@ -983,7 +970,7 @@ func TestACLDevice1CanAccessDevice2(t *testing.T) { test2ipURL, result, ) - assert.NoError(t, err) + assertNoErr(t, err) result, err = test1.Curl(test2ip6URL) assert.Lenf( @@ -994,7 +981,7 @@ func TestACLDevice1CanAccessDevice2(t *testing.T) { test2ip6URL, result, ) - assert.NoError(t, err) + assertNoErr(t, err) result, err = test1.Curl(test2fqdnURL) assert.Lenf( @@ -1005,7 +992,7 @@ func TestACLDevice1CanAccessDevice2(t *testing.T) { test2fqdnURL, result, ) - assert.NoError(t, err) + assertNoErr(t, err) result, err = test2.Curl(test1ipURL) assert.Empty(t, result) @@ -1018,9 +1005,6 @@ func TestACLDevice1CanAccessDevice2(t *testing.T) { result, err = test2.Curl(test1fqdnURL) assert.Empty(t, result) assert.Error(t, err) - - err = scenario.Shutdown() - assert.NoError(t, err) }) } } diff --git a/integration/auth_oidc_test.go b/integration/auth_oidc_test.go index 9b05af93..3368825b 100644 --- a/integration/auth_oidc_test.go +++ b/integration/auth_oidc_test.go @@ -42,22 +42,19 @@ func TestOIDCAuthenticationPingAll(t *testing.T) { t.Parallel() baseScenario, err := NewScenario() - if err != nil { - t.Errorf("failed to create scenario: %s", err) - } + assertNoErr(t, err) scenario := AuthOIDCScenario{ Scenario: baseScenario, } + defer scenario.Shutdown() spec := map[string]int{ "user1": len(TailscaleVersions), } oidcConfig, err := scenario.runMockOIDC(defaultAccessTTL) - if err != nil { - t.Errorf("failed to run mock OIDC server: %s", err) - } + assertNoErrf(t, "failed to run mock OIDC server: %s", err) oidcMap := map[string]string{ "HEADSCALE_OIDC_ISSUER": oidcConfig.Issuer, @@ -74,24 +71,16 @@ func TestOIDCAuthenticationPingAll(t *testing.T) { hsic.WithHostnameAsServerURL(), hsic.WithFileInContainer("/tmp/hs_client_oidc_secret", []byte(oidcConfig.ClientSecret)), ) - if err != nil { - t.Errorf("failed to create headscale environment: %s", err) - } + assertNoErrHeadscaleEnv(t, err) allClients, err := scenario.ListTailscaleClients() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClients(t, err) allIps, err := scenario.ListTailscaleClientsIPs() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClientIPs(t, err) err = scenario.WaitForTailscaleSync() - if err != nil { - t.Errorf("failed wait for tailscale clients to be in sync: %s", err) - } + assertNoErrSync(t, err) allAddrs := lo.Map(allIps, func(x netip.Addr, index int) string { return x.String() @@ -99,11 +88,6 @@ func TestOIDCAuthenticationPingAll(t *testing.T) { success := pingAllHelper(t, allClients, allAddrs) t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps)) - - err = scenario.Shutdown() - if err != nil { - t.Errorf("failed to tear down scenario: %s", err) - } } func TestOIDCExpireNodesBasedOnTokenExpiry(t *testing.T) { @@ -113,22 +97,19 @@ func TestOIDCExpireNodesBasedOnTokenExpiry(t *testing.T) { shortAccessTTL := 5 * time.Minute baseScenario, err := NewScenario() - if err != nil { - t.Errorf("failed to create scenario: %s", err) - } + assertNoErr(t, err) scenario := AuthOIDCScenario{ Scenario: baseScenario, } + defer scenario.Shutdown() spec := map[string]int{ "user1": len(TailscaleVersions), } oidcConfig, err := scenario.runMockOIDC(shortAccessTTL) - if err != nil { - t.Fatalf("failed to run mock OIDC server: %s", err) - } + assertNoErrf(t, "failed to run mock OIDC server: %s", err) oidcMap := map[string]string{ "HEADSCALE_OIDC_ISSUER": oidcConfig.Issuer, @@ -144,24 +125,16 @@ func TestOIDCExpireNodesBasedOnTokenExpiry(t *testing.T) { hsic.WithConfigEnv(oidcMap), hsic.WithHostnameAsServerURL(), ) - if err != nil { - t.Errorf("failed to create headscale environment: %s", err) - } + assertNoErrHeadscaleEnv(t, err) allClients, err := scenario.ListTailscaleClients() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClients(t, err) allIps, err := scenario.ListTailscaleClientsIPs() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClientIPs(t, err) err = scenario.WaitForTailscaleSync() - if err != nil { - t.Errorf("failed wait for tailscale clients to be in sync: %s", err) - } + assertNoErrSync(t, err) allAddrs := lo.Map(allIps, func(x netip.Addr, index int) string { return x.String() @@ -171,12 +144,8 @@ func TestOIDCExpireNodesBasedOnTokenExpiry(t *testing.T) { t.Logf("%d successful pings out of %d (before expiry)", success, len(allClients)*len(allIps)) // await all nodes being logged out after OIDC token expiry - scenario.WaitForTailscaleLogout() - - err = scenario.Shutdown() - if err != nil { - t.Errorf("failed to tear down scenario: %s", err) - } + err = scenario.WaitForTailscaleLogout() + assertNoErrLogout(t, err) } func (s *AuthOIDCScenario) CreateHeadscaleEnv( @@ -188,7 +157,7 @@ func (s *AuthOIDCScenario) CreateHeadscaleEnv( return err } - err = headscale.WaitForReady() + err = headscale.WaitForRunning() if err != nil { return err } @@ -311,15 +280,11 @@ func (s *AuthOIDCScenario) runTailscaleUp( log.Printf("running tailscale up for user %s", userStr) if user, ok := s.users[userStr]; ok { for _, client := range user.Clients { - user.joinWaitGroup.Add(1) - - go func(c TailscaleClient) { - defer user.joinWaitGroup.Done() - - // TODO(juanfont): error handle this - loginURL, err := c.UpWithLoginURL(loginServer) + c := client + user.joinWaitGroup.Go(func() error { + loginURL, err := c.LoginWithURL(loginServer) if err != nil { - log.Printf("failed to run tailscale up: %s", err) + log.Printf("%s failed to run tailscale up: %s", c.Hostname(), err) } loginURL.Host = fmt.Sprintf("%s:8080", headscale.GetIP()) @@ -336,9 +301,14 @@ func (s *AuthOIDCScenario) runTailscaleUp( req, _ := http.NewRequestWithContext(ctx, http.MethodGet, loginURL.String(), nil) resp, err := httpClient.Do(req) if err != nil { - log.Printf("%s failed to get login url %s: %s", c.Hostname(), loginURL, err) + log.Printf( + "%s failed to get login url %s: %s", + c.Hostname(), + loginURL, + err, + ) - return + return err } defer resp.Body.Close() @@ -347,28 +317,29 @@ func (s *AuthOIDCScenario) runTailscaleUp( if err != nil { log.Printf("%s failed to read response body: %s", c.Hostname(), err) - return + return err } log.Printf("Finished request for %s to join tailnet", c.Hostname()) - }(client) - err = client.WaitForReady() - if err != nil { - log.Printf("error waiting for client %s to be ready: %s", client.Hostname(), err) - } + return nil + }) log.Printf("client %s is ready", client.Hostname()) } - user.joinWaitGroup.Wait() + if err := user.joinWaitGroup.Wait(); err != nil { + return err + } for _, client := range user.Clients { - err := client.WaitForReady() + err := client.WaitForRunning() if err != nil { - log.Printf("client %s was not ready: %s", client.Hostname(), err) - - return fmt.Errorf("failed to up tailscale node: %w", err) + return fmt.Errorf( + "%s tailscale node has not reached running: %w", + client.Hostname(), + err, + ) } } @@ -378,11 +349,11 @@ func (s *AuthOIDCScenario) runTailscaleUp( return fmt.Errorf("failed to up tailscale node: %w", errNoUserAvailable) } -func (s *AuthOIDCScenario) Shutdown() error { +func (s *AuthOIDCScenario) Shutdown() { err := s.pool.Purge(s.mockOIDC) if err != nil { - return err + log.Printf("failed to remove mock oidc container") } - return s.Scenario.Shutdown() + s.Scenario.Shutdown() } diff --git a/integration/auth_web_flow_test.go b/integration/auth_web_flow_test.go index 7e497f68..22f8d1e4 100644 --- a/integration/auth_web_flow_test.go +++ b/integration/auth_web_flow_test.go @@ -28,12 +28,13 @@ func TestAuthWebFlowAuthenticationPingAll(t *testing.T) { baseScenario, err := NewScenario() if err != nil { - t.Errorf("failed to create scenario: %s", err) + t.Fatalf("failed to create scenario: %s", err) } scenario := AuthWebFlowScenario{ Scenario: baseScenario, } + defer scenario.Shutdown() spec := map[string]int{ "user1": len(TailscaleVersions), @@ -41,24 +42,16 @@ func TestAuthWebFlowAuthenticationPingAll(t *testing.T) { } err = scenario.CreateHeadscaleEnv(spec, hsic.WithTestName("webauthping")) - if err != nil { - t.Errorf("failed to create headscale environment: %s", err) - } + assertNoErrHeadscaleEnv(t, err) allClients, err := scenario.ListTailscaleClients() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClients(t, err) allIps, err := scenario.ListTailscaleClientsIPs() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClientIPs(t, err) err = scenario.WaitForTailscaleSync() - if err != nil { - t.Errorf("failed wait for tailscale clients to be in sync: %s", err) - } + assertNoErrSync(t, err) allAddrs := lo.Map(allIps, func(x netip.Addr, index int) string { return x.String() @@ -66,11 +59,6 @@ func TestAuthWebFlowAuthenticationPingAll(t *testing.T) { success := pingAllHelper(t, allClients, allAddrs) t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps)) - - err = scenario.Shutdown() - if err != nil { - t.Errorf("failed to tear down scenario: %s", err) - } } func TestAuthWebFlowLogoutAndRelogin(t *testing.T) { @@ -78,13 +66,12 @@ func TestAuthWebFlowLogoutAndRelogin(t *testing.T) { t.Parallel() baseScenario, err := NewScenario() - if err != nil { - t.Errorf("failed to create scenario: %s", err) - } + assertNoErr(t, err) scenario := AuthWebFlowScenario{ Scenario: baseScenario, } + defer scenario.Shutdown() spec := map[string]int{ "user1": len(TailscaleVersions), @@ -92,24 +79,16 @@ func TestAuthWebFlowLogoutAndRelogin(t *testing.T) { } err = scenario.CreateHeadscaleEnv(spec, hsic.WithTestName("weblogout")) - if err != nil { - t.Errorf("failed to create headscale environment: %s", err) - } + assertNoErrHeadscaleEnv(t, err) allClients, err := scenario.ListTailscaleClients() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClients(t, err) allIps, err := scenario.ListTailscaleClientsIPs() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClientIPs(t, err) err = scenario.WaitForTailscaleSync() - if err != nil { - t.Errorf("failed wait for tailscale clients to be in sync: %s", err) - } + assertNoErrSync(t, err) allAddrs := lo.Map(allIps, func(x netip.Addr, index int) string { return x.String() @@ -122,7 +101,7 @@ func TestAuthWebFlowLogoutAndRelogin(t *testing.T) { for _, client := range allClients { ips, err := client.IPs() if err != nil { - t.Errorf("failed to get IPs for client %s: %s", client.Hostname(), err) + t.Fatalf("failed to get IPs for client %s: %s", client.Hostname(), err) } clientIPs[client] = ips } @@ -130,37 +109,32 @@ func TestAuthWebFlowLogoutAndRelogin(t *testing.T) { for _, client := range allClients { err := client.Logout() if err != nil { - t.Errorf("failed to logout client %s: %s", client.Hostname(), err) + t.Fatalf("failed to logout client %s: %s", client.Hostname(), err) } } - scenario.WaitForTailscaleLogout() + err = scenario.WaitForTailscaleLogout() + assertNoErrLogout(t, err) t.Logf("all clients logged out") headscale, err := scenario.Headscale() - if err != nil { - t.Errorf("failed to get headscale server: %s", err) - } + assertNoErrGetHeadscale(t, err) for userName := range spec { err = scenario.runTailscaleUp(userName, headscale.GetEndpoint()) if err != nil { - t.Errorf("failed to run tailscale up: %s", err) + t.Fatalf("failed to run tailscale up: %s", err) } } t.Logf("all clients logged in again") allClients, err = scenario.ListTailscaleClients() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClients(t, err) allIps, err = scenario.ListTailscaleClientsIPs() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClientIPs(t, err) allAddrs = lo.Map(allIps, func(x netip.Addr, index int) string { return x.String() @@ -172,12 +146,12 @@ func TestAuthWebFlowLogoutAndRelogin(t *testing.T) { for _, client := range allClients { ips, err := client.IPs() if err != nil { - t.Errorf("failed to get IPs for client %s: %s", client.Hostname(), err) + t.Fatalf("failed to get IPs for client %s: %s", client.Hostname(), err) } // lets check if the IPs are the same if len(ips) != len(clientIPs[client]) { - t.Errorf("IPs changed for client %s", client.Hostname()) + t.Fatalf("IPs changed for client %s", client.Hostname()) } for _, ip := range ips { @@ -191,7 +165,7 @@ func TestAuthWebFlowLogoutAndRelogin(t *testing.T) { } if !found { - t.Errorf( + t.Fatalf( "IPs changed for client %s. Used to be %v now %v", client.Hostname(), clientIPs[client], @@ -202,11 +176,6 @@ func TestAuthWebFlowLogoutAndRelogin(t *testing.T) { } t.Logf("all clients IPs are the same") - - err = scenario.Shutdown() - if err != nil { - t.Errorf("failed to tear down scenario: %s", err) - } } func (s *AuthWebFlowScenario) CreateHeadscaleEnv( @@ -218,7 +187,7 @@ func (s *AuthWebFlowScenario) CreateHeadscaleEnv( return err } - err = headscale.WaitForReady() + err = headscale.WaitForRunning() if err != nil { return err } @@ -250,36 +219,39 @@ func (s *AuthWebFlowScenario) runTailscaleUp( log.Printf("running tailscale up for user %s", userStr) if user, ok := s.users[userStr]; ok { for _, client := range user.Clients { - user.joinWaitGroup.Add(1) - - go func(c TailscaleClient) { - defer user.joinWaitGroup.Done() - - // TODO(juanfont): error handle this - loginURL, err := c.UpWithLoginURL(loginServer) + c := client + user.joinWaitGroup.Go(func() error { + loginURL, err := c.LoginWithURL(loginServer) if err != nil { - log.Printf("failed to run tailscale up: %s", err) + log.Printf("failed to run tailscale up (%s): %s", c.Hostname(), err) + + return err } err = s.runHeadscaleRegister(userStr, loginURL) if err != nil { - log.Printf("failed to register client: %s", err) - } - }(client) + log.Printf("failed to register client (%s): %s", c.Hostname(), err) - err := client.WaitForReady() + return err + } + + return nil + }) + + err := client.WaitForRunning() if err != nil { log.Printf("error waiting for client %s to be ready: %s", client.Hostname(), err) } } - user.joinWaitGroup.Wait() + + if err := user.joinWaitGroup.Wait(); err != nil { + return err + } for _, client := range user.Clients { - err := client.WaitForReady() + err := client.WaitForRunning() if err != nil { - log.Printf("client %s was not ready: %s", client.Hostname(), err) - - return fmt.Errorf("failed to up tailscale node: %w", err) + return fmt.Errorf("%s failed to up tailscale node: %w", client.Hostname(), err) } } diff --git a/integration/cli_test.go b/integration/cli_test.go index d4525883..5a27d517 100644 --- a/integration/cli_test.go +++ b/integration/cli_test.go @@ -33,7 +33,8 @@ func TestUserCommand(t *testing.T) { t.Parallel() scenario, err := NewScenario() - assert.NoError(t, err) + assertNoErr(t, err) + defer scenario.Shutdown() spec := map[string]int{ "user1": 0, @@ -41,10 +42,10 @@ func TestUserCommand(t *testing.T) { } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) - assert.NoError(t, err) + assertNoErr(t, err) headscale, err := scenario.Headscale() - assert.NoError(t, err) + assertNoErr(t, err) var listUsers []v1.User err = executeAndUnmarshal(headscale, @@ -57,7 +58,7 @@ func TestUserCommand(t *testing.T) { }, &listUsers, ) - assert.NoError(t, err) + assertNoErr(t, err) result := []string{listUsers[0].Name, listUsers[1].Name} sort.Strings(result) @@ -79,7 +80,7 @@ func TestUserCommand(t *testing.T) { "newname", }, ) - assert.NoError(t, err) + assertNoErr(t, err) var listAfterRenameUsers []v1.User err = executeAndUnmarshal(headscale, @@ -92,7 +93,7 @@ func TestUserCommand(t *testing.T) { }, &listAfterRenameUsers, ) - assert.NoError(t, err) + assertNoErr(t, err) result = []string{listAfterRenameUsers[0].Name, listAfterRenameUsers[1].Name} sort.Strings(result) @@ -102,9 +103,6 @@ func TestUserCommand(t *testing.T) { []string{"newname", "user1"}, result, ) - - err = scenario.Shutdown() - assert.NoError(t, err) } func TestPreAuthKeyCommand(t *testing.T) { @@ -115,20 +113,21 @@ func TestPreAuthKeyCommand(t *testing.T) { count := 3 scenario, err := NewScenario() - assert.NoError(t, err) + assertNoErr(t, err) + defer scenario.Shutdown() spec := map[string]int{ user: 0, } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clipak")) - assert.NoError(t, err) + assertNoErr(t, err) headscale, err := scenario.Headscale() - assert.NoError(t, err) + assertNoErr(t, err) keys := make([]*v1.PreAuthKey, count) - assert.NoError(t, err) + assertNoErr(t, err) for index := 0; index < count; index++ { var preAuthKey v1.PreAuthKey @@ -150,7 +149,7 @@ func TestPreAuthKeyCommand(t *testing.T) { }, &preAuthKey, ) - assert.NoError(t, err) + assertNoErr(t, err) keys[index] = &preAuthKey } @@ -171,7 +170,7 @@ func TestPreAuthKeyCommand(t *testing.T) { }, &listedPreAuthKeys, ) - assert.NoError(t, err) + assertNoErr(t, err) // There is one key created by "scenario.CreateHeadscaleEnv" assert.Len(t, listedPreAuthKeys, 4) @@ -222,7 +221,7 @@ func TestPreAuthKeyCommand(t *testing.T) { listedPreAuthKeys[1].Key, }, ) - assert.NoError(t, err) + assertNoErr(t, err) var listedPreAuthKeysAfterExpire []v1.PreAuthKey err = executeAndUnmarshal( @@ -238,14 +237,11 @@ func TestPreAuthKeyCommand(t *testing.T) { }, &listedPreAuthKeysAfterExpire, ) - assert.NoError(t, err) + assertNoErr(t, err) assert.True(t, listedPreAuthKeysAfterExpire[1].Expiration.AsTime().Before(time.Now())) assert.True(t, listedPreAuthKeysAfterExpire[2].Expiration.AsTime().After(time.Now())) assert.True(t, listedPreAuthKeysAfterExpire[3].Expiration.AsTime().After(time.Now())) - - err = scenario.Shutdown() - assert.NoError(t, err) } func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) { @@ -255,17 +251,18 @@ func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) { user := "pre-auth-key-without-exp-user" scenario, err := NewScenario() - assert.NoError(t, err) + assertNoErr(t, err) + defer scenario.Shutdown() spec := map[string]int{ user: 0, } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clipaknaexp")) - assert.NoError(t, err) + assertNoErr(t, err) headscale, err := scenario.Headscale() - assert.NoError(t, err) + assertNoErr(t, err) var preAuthKey v1.PreAuthKey err = executeAndUnmarshal( @@ -282,7 +279,7 @@ func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) { }, &preAuthKey, ) - assert.NoError(t, err) + assertNoErr(t, err) var listedPreAuthKeys []v1.PreAuthKey err = executeAndUnmarshal( @@ -298,7 +295,7 @@ func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) { }, &listedPreAuthKeys, ) - assert.NoError(t, err) + assertNoErr(t, err) // There is one key created by "scenario.CreateHeadscaleEnv" assert.Len(t, listedPreAuthKeys, 2) @@ -308,9 +305,6 @@ func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) { t, listedPreAuthKeys[1].Expiration.AsTime().Before(time.Now().Add(time.Minute*70)), ) - - err = scenario.Shutdown() - assert.NoError(t, err) } func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) { @@ -320,17 +314,18 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) { user := "pre-auth-key-reus-ephm-user" scenario, err := NewScenario() - assert.NoError(t, err) + assertNoErr(t, err) + defer scenario.Shutdown() spec := map[string]int{ user: 0, } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clipakresueeph")) - assert.NoError(t, err) + assertNoErr(t, err) headscale, err := scenario.Headscale() - assert.NoError(t, err) + assertNoErr(t, err) var preAuthReusableKey v1.PreAuthKey err = executeAndUnmarshal( @@ -347,7 +342,7 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) { }, &preAuthReusableKey, ) - assert.NoError(t, err) + assertNoErr(t, err) var preAuthEphemeralKey v1.PreAuthKey err = executeAndUnmarshal( @@ -364,7 +359,7 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) { }, &preAuthEphemeralKey, ) - assert.NoError(t, err) + assertNoErr(t, err) assert.True(t, preAuthEphemeralKey.GetEphemeral()) assert.False(t, preAuthEphemeralKey.GetReusable()) @@ -383,13 +378,10 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) { }, &listedPreAuthKeys, ) - assert.NoError(t, err) + assertNoErr(t, err) // There is one key created by "scenario.CreateHeadscaleEnv" assert.Len(t, listedPreAuthKeys, 3) - - err = scenario.Shutdown() - assert.NoError(t, err) } func TestEnablingRoutes(t *testing.T) { @@ -399,27 +391,24 @@ func TestEnablingRoutes(t *testing.T) { user := "enable-routing" scenario, err := NewScenario() - assert.NoError(t, err) + assertNoErrf(t, "failed to create scenario: %s", err) + defer scenario.Shutdown() spec := map[string]int{ user: 3, } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clienableroute")) - assert.NoError(t, err) + assertNoErrHeadscaleEnv(t, err) allClients, err := scenario.ListTailscaleClients() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClients(t, err) err = scenario.WaitForTailscaleSync() - if err != nil { - t.Errorf("failed wait for tailscale clients to be in sync: %s", err) - } + assertNoErrSync(t, err) headscale, err := scenario.Headscale() - assert.NoError(t, err) + assertNoErrGetHeadscale(t, err) // advertise routes using the up command for i, client := range allClients { @@ -432,13 +421,11 @@ func TestEnablingRoutes(t *testing.T) { "-login-server", headscale.GetEndpoint(), "--hostname", hostname, }) - assert.NoError(t, err) + assertNoErrf(t, "failed to advertise route: %s", err) } err = scenario.WaitForTailscaleSync() - if err != nil { - t.Errorf("failed wait for tailscale clients to be in sync: %s", err) - } + assertNoErrSync(t, err) var routes []*v1.Route err = executeAndUnmarshal( @@ -453,7 +440,7 @@ func TestEnablingRoutes(t *testing.T) { &routes, ) - assert.NoError(t, err) + assertNoErr(t, err) assert.Len(t, routes, 3) for _, route := range routes { @@ -471,7 +458,7 @@ func TestEnablingRoutes(t *testing.T) { "--route", strconv.Itoa(int(route.Id)), }) - assert.NoError(t, err) + assertNoErr(t, err) } var enablingRoutes []*v1.Route @@ -486,7 +473,7 @@ func TestEnablingRoutes(t *testing.T) { }, &enablingRoutes, ) - assert.NoError(t, err) + assertNoErr(t, err) for _, route := range enablingRoutes { assert.Equal(t, route.Advertised, true) @@ -504,7 +491,7 @@ func TestEnablingRoutes(t *testing.T) { "--route", strconv.Itoa(int(routeIDToBeDisabled)), }) - assert.NoError(t, err) + assertNoErr(t, err) var disablingRoutes []*v1.Route err = executeAndUnmarshal( @@ -518,7 +505,7 @@ func TestEnablingRoutes(t *testing.T) { }, &disablingRoutes, ) - assert.NoError(t, err) + assertNoErr(t, err) for _, route := range disablingRoutes { assert.Equal(t, true, route.Advertised) @@ -540,7 +527,8 @@ func TestApiKeyCommand(t *testing.T) { count := 5 scenario, err := NewScenario() - assert.NoError(t, err) + assertNoErr(t, err) + defer scenario.Shutdown() spec := map[string]int{ "user1": 0, @@ -548,10 +536,10 @@ func TestApiKeyCommand(t *testing.T) { } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) - assert.NoError(t, err) + assertNoErr(t, err) headscale, err := scenario.Headscale() - assert.NoError(t, err) + assertNoErr(t, err) keys := make([]string, count) @@ -675,9 +663,6 @@ func TestApiKeyCommand(t *testing.T) { ) } } - - err = scenario.Shutdown() - assert.NoError(t, err) } func TestNodeTagCommand(t *testing.T) { @@ -685,17 +670,18 @@ func TestNodeTagCommand(t *testing.T) { t.Parallel() scenario, err := NewScenario() - assert.NoError(t, err) + assertNoErr(t, err) + defer scenario.Shutdown() spec := map[string]int{ "user1": 0, } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) - assert.NoError(t, err) + assertNoErr(t, err) headscale, err := scenario.Headscale() - assert.NoError(t, err) + assertNoErr(t, err) machineKeys := []string{ "nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe", @@ -810,9 +796,6 @@ func TestNodeTagCommand(t *testing.T) { found, "should find a machine with the tag 'tag:test' in the list of machines", ) - - err = scenario.Shutdown() - assert.NoError(t, err) } func TestNodeCommand(t *testing.T) { @@ -820,7 +803,8 @@ func TestNodeCommand(t *testing.T) { t.Parallel() scenario, err := NewScenario() - assert.NoError(t, err) + assertNoErr(t, err) + defer scenario.Shutdown() spec := map[string]int{ "machine-user": 0, @@ -828,10 +812,10 @@ func TestNodeCommand(t *testing.T) { } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) - assert.NoError(t, err) + assertNoErr(t, err) headscale, err := scenario.Headscale() - assert.NoError(t, err) + assertNoErr(t, err) // Randomly generated machine keys machineKeys := []string{ @@ -1053,9 +1037,6 @@ func TestNodeCommand(t *testing.T) { assert.Nil(t, err) assert.Len(t, listOnlyMachineUserAfterDelete, 4) - - err = scenario.Shutdown() - assert.NoError(t, err) } func TestNodeExpireCommand(t *testing.T) { @@ -1063,17 +1044,18 @@ func TestNodeExpireCommand(t *testing.T) { t.Parallel() scenario, err := NewScenario() - assert.NoError(t, err) + assertNoErr(t, err) + defer scenario.Shutdown() spec := map[string]int{ "machine-expire-user": 0, } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) - assert.NoError(t, err) + assertNoErr(t, err) headscale, err := scenario.Headscale() - assert.NoError(t, err) + assertNoErr(t, err) // Randomly generated machine keys machineKeys := []string{ @@ -1182,9 +1164,6 @@ func TestNodeExpireCommand(t *testing.T) { assert.True(t, listAllAfterExpiry[2].Expiry.AsTime().Before(time.Now())) assert.True(t, listAllAfterExpiry[3].Expiry.AsTime().IsZero()) assert.True(t, listAllAfterExpiry[4].Expiry.AsTime().IsZero()) - - err = scenario.Shutdown() - assert.NoError(t, err) } func TestNodeRenameCommand(t *testing.T) { @@ -1192,17 +1171,18 @@ func TestNodeRenameCommand(t *testing.T) { t.Parallel() scenario, err := NewScenario() - assert.NoError(t, err) + assertNoErr(t, err) + defer scenario.Shutdown() spec := map[string]int{ "machine-rename-command": 0, } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) - assert.NoError(t, err) + assertNoErr(t, err) headscale, err := scenario.Headscale() - assert.NoError(t, err) + assertNoErr(t, err) // Randomly generated machine keys machineKeys := []string{ @@ -1349,9 +1329,6 @@ func TestNodeRenameCommand(t *testing.T) { assert.Equal(t, "newmachine-3", listAllAfterRenameAttempt[2].GetGivenName()) assert.Contains(t, listAllAfterRenameAttempt[3].GetGivenName(), "machine-4") assert.Contains(t, listAllAfterRenameAttempt[4].GetGivenName(), "machine-5") - - err = scenario.Shutdown() - assert.NoError(t, err) } func TestNodeMoveCommand(t *testing.T) { @@ -1359,7 +1336,8 @@ func TestNodeMoveCommand(t *testing.T) { t.Parallel() scenario, err := NewScenario() - assert.NoError(t, err) + assertNoErr(t, err) + defer scenario.Shutdown() spec := map[string]int{ "old-user": 0, @@ -1367,10 +1345,10 @@ func TestNodeMoveCommand(t *testing.T) { } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) - assert.NoError(t, err) + assertNoErr(t, err) headscale, err := scenario.Headscale() - assert.NoError(t, err) + assertNoErr(t, err) // Randomly generated machine key machineKey := "nodekey:688411b767663479632d44140f08a9fde87383adc7cdeb518f62ce28a17ef0aa" @@ -1514,7 +1492,4 @@ func TestNodeMoveCommand(t *testing.T) { assert.Nil(t, err) assert.Equal(t, machine.User.Name, "old-user") - - err = scenario.Shutdown() - assert.NoError(t, err) } diff --git a/integration/control.go b/integration/control.go index 65432b3f..b4110070 100644 --- a/integration/control.go +++ b/integration/control.go @@ -13,7 +13,7 @@ type ControlServer interface { ConnectToNetwork(network *dockertest.Network) error GetHealthEndpoint() string GetEndpoint() string - WaitForReady() error + WaitForRunning() error CreateUser(user string) error CreateAuthKey(user string, reusable bool, ephemeral bool) (*v1.PreAuthKey, error) ListMachinesInUser(user string) ([]*v1.Machine, error) diff --git a/integration/embedded_derp_test.go b/integration/embedded_derp_test.go index e9183cdc..17e8310b 100644 --- a/integration/embedded_derp_test.go +++ b/integration/embedded_derp_test.go @@ -24,14 +24,13 @@ func TestDERPServerScenario(t *testing.T) { // t.Parallel() baseScenario, err := NewScenario() - if err != nil { - t.Errorf("failed to create scenario: %s", err) - } + assertNoErr(t, err) scenario := EmbeddedDERPServerScenario{ Scenario: baseScenario, tsicNetworks: map[string]*dockertest.Network{}, } + defer scenario.Shutdown() spec := map[string]int{ "user1": len(TailscaleVersions), @@ -53,39 +52,23 @@ func TestDERPServerScenario(t *testing.T) { hsic.WithTLS(), hsic.WithHostnameAsServerURL(), ) - - if err != nil { - t.Errorf("failed to create headscale environment: %s", err) - } + assertNoErrHeadscaleEnv(t, err) allClients, err := scenario.ListTailscaleClients() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClients(t, err) allIps, err := scenario.ListTailscaleClientsIPs() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClientIPs(t, err) err = scenario.WaitForTailscaleSync() - if err != nil { - t.Errorf("failed wait for tailscale clients to be in sync: %s", err) - } + assertNoErrSync(t, err) allHostnames, err := scenario.ListTailscaleClientsFQDNs() - if err != nil { - t.Errorf("failed to get FQDNs: %s", err) - } + assertNoErrListFQDN(t, err) success := pingDerpAllHelper(t, allClients, allHostnames) t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps)) - - err = scenario.Shutdown() - if err != nil { - t.Errorf("failed to tear down scenario: %s", err) - } } func (s *EmbeddedDERPServerScenario) CreateHeadscaleEnv( @@ -105,7 +88,7 @@ func (s *EmbeddedDERPServerScenario) CreateHeadscaleEnv( headscaleURL.Host = fmt.Sprintf("%s:%s", hsServer.GetHostname(), headscaleURL.Port()) - err = hsServer.WaitForReady() + err = hsServer.WaitForRunning() if err != nil { return err } @@ -186,16 +169,11 @@ func (s *EmbeddedDERPServerScenario) CreateTailscaleIsolatedNodesInUser( cert := hsServer.GetCert() - user.createWaitGroup.Add(1) - opts = append(opts, tsic.WithHeadscaleTLS(cert), ) - go func() { - defer user.createWaitGroup.Done() - - // TODO(kradalby): error handle this + user.createWaitGroup.Go(func() error { tsClient, err := tsic.New( s.pool, version, @@ -203,34 +181,45 @@ func (s *EmbeddedDERPServerScenario) CreateTailscaleIsolatedNodesInUser( opts..., ) if err != nil { - // return fmt.Errorf("failed to add tailscale node: %w", err) - log.Printf("failed to create tailscale node: %s", err) + return fmt.Errorf( + "failed to create tailscale (%s) node: %w", + tsClient.Hostname(), + err, + ) } - err = tsClient.WaitForReady() + err = tsClient.WaitForNeedsLogin() if err != nil { - // return fmt.Errorf("failed to add tailscale node: %w", err) - log.Printf("failed to wait for tailscaled: %s", err) + return fmt.Errorf( + "failed to wait for tailscaled (%s) to need login: %w", + tsClient.Hostname(), + err, + ) } user.Clients[tsClient.Hostname()] = tsClient - }() + + return nil + }) + } + + if err := user.createWaitGroup.Wait(); err != nil { + return err } - user.createWaitGroup.Wait() return nil } - return fmt.Errorf("failed to add tailscale node: %w", errNoUserAvailable) + return fmt.Errorf("failed to add tailscale nodes: %w", errNoUserAvailable) } -func (s *EmbeddedDERPServerScenario) Shutdown() error { +func (s *EmbeddedDERPServerScenario) Shutdown() { for _, network := range s.tsicNetworks { err := s.pool.RemoveNetwork(network) if err != nil { - return err + log.Printf("failed to remove DERP network %s", network.Network.Name) } } - return s.Scenario.Shutdown() + s.Scenario.Shutdown() } diff --git a/integration/general_test.go b/integration/general_test.go index f3187e3a..20eb3bde 100644 --- a/integration/general_test.go +++ b/integration/general_test.go @@ -21,9 +21,8 @@ func TestPingAllByIP(t *testing.T) { t.Parallel() scenario, err := NewScenario() - if err != nil { - t.Errorf("failed to create scenario: %s", err) - } + assertNoErr(t, err) + defer scenario.Shutdown() spec := map[string]int{ "user1": len(TailscaleVersions), @@ -31,24 +30,16 @@ func TestPingAllByIP(t *testing.T) { } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("pingallbyip")) - if err != nil { - t.Errorf("failed to create headscale environment: %s", err) - } + assertNoErrHeadscaleEnv(t, err) allClients, err := scenario.ListTailscaleClients() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClients(t, err) allIps, err := scenario.ListTailscaleClientsIPs() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClientIPs(t, err) err = scenario.WaitForTailscaleSync() - if err != nil { - t.Errorf("failed wait for tailscale clients to be in sync: %s", err) - } + assertNoErrSync(t, err) allAddrs := lo.Map(allIps, func(x netip.Addr, index int) string { return x.String() @@ -56,11 +47,6 @@ func TestPingAllByIP(t *testing.T) { success := pingAllHelper(t, allClients, allAddrs) t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps)) - - err = scenario.Shutdown() - if err != nil { - t.Errorf("failed to tear down scenario: %s", err) - } } func TestAuthKeyLogoutAndRelogin(t *testing.T) { @@ -68,9 +54,8 @@ func TestAuthKeyLogoutAndRelogin(t *testing.T) { t.Parallel() scenario, err := NewScenario() - if err != nil { - t.Errorf("failed to create scenario: %s", err) - } + assertNoErr(t, err) + defer scenario.Shutdown() spec := map[string]int{ "user1": len(TailscaleVersions), @@ -78,25 +63,19 @@ func TestAuthKeyLogoutAndRelogin(t *testing.T) { } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("pingallbyip")) - if err != nil { - t.Errorf("failed to create headscale environment: %s", err) - } + assertNoErrHeadscaleEnv(t, err) allClients, err := scenario.ListTailscaleClients() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClients(t, err) err = scenario.WaitForTailscaleSync() - if err != nil { - t.Errorf("failed wait for tailscale clients to be in sync: %s", err) - } + assertNoErrSync(t, err) clientIPs := make(map[TailscaleClient][]netip.Addr) for _, client := range allClients { ips, err := client.IPs() if err != nil { - t.Errorf("failed to get IPs for client %s: %s", client.Hostname(), err) + t.Fatalf("failed to get IPs for client %s: %s", client.Hostname(), err) } clientIPs[client] = ips } @@ -104,45 +83,38 @@ func TestAuthKeyLogoutAndRelogin(t *testing.T) { for _, client := range allClients { err := client.Logout() if err != nil { - t.Errorf("failed to logout client %s: %s", client.Hostname(), err) + t.Fatalf("failed to logout client %s: %s", client.Hostname(), err) } } - scenario.WaitForTailscaleLogout() + err = scenario.WaitForTailscaleLogout() + assertNoErrLogout(t, err) t.Logf("all clients logged out") headscale, err := scenario.Headscale() - if err != nil { - t.Errorf("failed to get headscale server: %s", err) - } + assertNoErrGetHeadscale(t, err) for userName := range spec { key, err := scenario.CreatePreAuthKey(userName, true, false) if err != nil { - t.Errorf("failed to create pre-auth key for user %s: %s", userName, err) + t.Fatalf("failed to create pre-auth key for user %s: %s", userName, err) } err = scenario.RunTailscaleUp(userName, headscale.GetEndpoint(), key.GetKey()) if err != nil { - t.Errorf("failed to run tailscale up for user %s: %s", userName, err) + t.Fatalf("failed to run tailscale up for user %s: %s", userName, err) } } err = scenario.WaitForTailscaleSync() - if err != nil { - t.Errorf("failed wait for tailscale clients to be in sync: %s", err) - } + assertNoErrSync(t, err) allClients, err = scenario.ListTailscaleClients() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClients(t, err) allIps, err := scenario.ListTailscaleClientsIPs() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClientIPs(t, err) allAddrs := lo.Map(allIps, func(x netip.Addr, index int) string { return x.String() @@ -154,12 +126,12 @@ func TestAuthKeyLogoutAndRelogin(t *testing.T) { for _, client := range allClients { ips, err := client.IPs() if err != nil { - t.Errorf("failed to get IPs for client %s: %s", client.Hostname(), err) + t.Fatalf("failed to get IPs for client %s: %s", client.Hostname(), err) } // lets check if the IPs are the same if len(ips) != len(clientIPs[client]) { - t.Errorf("IPs changed for client %s", client.Hostname()) + t.Fatalf("IPs changed for client %s", client.Hostname()) } for _, ip := range ips { @@ -173,7 +145,7 @@ func TestAuthKeyLogoutAndRelogin(t *testing.T) { } if !found { - t.Errorf( + t.Fatalf( "IPs changed for client %s. Used to be %v now %v", client.Hostname(), clientIPs[client], @@ -182,13 +154,6 @@ func TestAuthKeyLogoutAndRelogin(t *testing.T) { } } } - - t.Logf("all clients IPs are the same") - - err = scenario.Shutdown() - if err != nil { - t.Errorf("failed to tear down scenario: %s", err) - } } func TestEphemeral(t *testing.T) { @@ -196,9 +161,8 @@ func TestEphemeral(t *testing.T) { t.Parallel() scenario, err := NewScenario() - if err != nil { - t.Errorf("failed to create scenario: %s", err) - } + assertNoErr(t, err) + defer scenario.Shutdown() spec := map[string]int{ "user1": len(TailscaleVersions), @@ -206,46 +170,38 @@ func TestEphemeral(t *testing.T) { } headscale, err := scenario.Headscale(hsic.WithTestName("ephemeral")) - if err != nil { - t.Errorf("failed to create headscale environment: %s", err) - } + assertNoErrHeadscaleEnv(t, err) for userName, clientCount := range spec { err = scenario.CreateUser(userName) if err != nil { - t.Errorf("failed to create user %s: %s", userName, err) + t.Fatalf("failed to create user %s: %s", userName, err) } err = scenario.CreateTailscaleNodesInUser(userName, "all", clientCount, []tsic.Option{}...) if err != nil { - t.Errorf("failed to create tailscale nodes in user %s: %s", userName, err) + t.Fatalf("failed to create tailscale nodes in user %s: %s", userName, err) } key, err := scenario.CreatePreAuthKey(userName, true, true) if err != nil { - t.Errorf("failed to create pre-auth key for user %s: %s", userName, err) + t.Fatalf("failed to create pre-auth key for user %s: %s", userName, err) } err = scenario.RunTailscaleUp(userName, headscale.GetEndpoint(), key.GetKey()) if err != nil { - t.Errorf("failed to run tailscale up for user %s: %s", userName, err) + t.Fatalf("failed to run tailscale up for user %s: %s", userName, err) } } err = scenario.WaitForTailscaleSync() - if err != nil { - t.Errorf("failed wait for tailscale clients to be in sync: %s", err) - } + assertNoErrSync(t, err) allClients, err := scenario.ListTailscaleClients() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClients(t, err) allIps, err := scenario.ListTailscaleClientsIPs() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClientIPs(t, err) allAddrs := lo.Map(allIps, func(x netip.Addr, index int) string { return x.String() @@ -257,11 +213,12 @@ func TestEphemeral(t *testing.T) { for _, client := range allClients { err := client.Logout() if err != nil { - t.Errorf("failed to logout client %s: %s", client.Hostname(), err) + t.Fatalf("failed to logout client %s: %s", client.Hostname(), err) } } - scenario.WaitForTailscaleLogout() + err = scenario.WaitForTailscaleLogout() + assertNoErrLogout(t, err) t.Logf("all clients logged out") @@ -277,14 +234,9 @@ func TestEphemeral(t *testing.T) { } if len(machines) != 0 { - t.Errorf("expected no machines, got %d in user %s", len(machines), userName) + t.Fatalf("expected no machines, got %d in user %s", len(machines), userName) } } - - err = scenario.Shutdown() - if err != nil { - t.Errorf("failed to tear down scenario: %s", err) - } } func TestPingAllByHostname(t *testing.T) { @@ -292,9 +244,8 @@ func TestPingAllByHostname(t *testing.T) { t.Parallel() scenario, err := NewScenario() - if err != nil { - t.Errorf("failed to create scenario: %s", err) - } + assertNoErr(t, err) + defer scenario.Shutdown() spec := map[string]int{ // Omit 1.16.2 (-1) because it does not have the FQDN field @@ -303,33 +254,20 @@ func TestPingAllByHostname(t *testing.T) { } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("pingallbyname")) - if err != nil { - t.Errorf("failed to create headscale environment: %s", err) - } + assertNoErrHeadscaleEnv(t, err) allClients, err := scenario.ListTailscaleClients() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClients(t, err) err = scenario.WaitForTailscaleSync() - if err != nil { - t.Errorf("failed wait for tailscale clients to be in sync: %s", err) - } + assertNoErrSync(t, err) allHostnames, err := scenario.ListTailscaleClientsFQDNs() - if err != nil { - t.Errorf("failed to get FQDNs: %s", err) - } + assertNoErrListFQDN(t, err) success := pingAllHelper(t, allClients, allHostnames) t.Logf("%d successful pings out of %d", success, len(allClients)*len(allClients)) - - err = scenario.Shutdown() - if err != nil { - t.Errorf("failed to tear down scenario: %s", err) - } } // If subtests are parallel, then they will start before setup is run. @@ -354,9 +292,8 @@ func TestTaildrop(t *testing.T) { } scenario, err := NewScenario() - if err != nil { - t.Errorf("failed to create scenario: %s", err) - } + assertNoErr(t, err) + defer scenario.Shutdown() spec := map[string]int{ // Omit 1.16.2 (-1) because it does not have the FQDN field @@ -364,31 +301,23 @@ func TestTaildrop(t *testing.T) { } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("taildrop")) - if err != nil { - t.Errorf("failed to create headscale environment: %s", err) - } + assertNoErrHeadscaleEnv(t, err) allClients, err := scenario.ListTailscaleClients() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClients(t, err) err = scenario.WaitForTailscaleSync() - if err != nil { - t.Errorf("failed wait for tailscale clients to be in sync: %s", err) - } + assertNoErrSync(t, err) // This will essentially fetch and cache all the FQDNs _, err = scenario.ListTailscaleClientsFQDNs() - if err != nil { - t.Errorf("failed to get FQDNs: %s", err) - } + assertNoErrListFQDN(t, err) for _, client := range allClients { command := []string{"touch", fmt.Sprintf("/tmp/file_from_%s", client.Hostname())} if _, _, err := client.Execute(command); err != nil { - t.Errorf("failed to create taildrop file on %s, err: %s", client.Hostname(), err) + t.Fatalf("failed to create taildrop file on %s, err: %s", client.Hostname(), err) } for _, peer := range allClients { @@ -417,7 +346,7 @@ func TestTaildrop(t *testing.T) { return err }) if err != nil { - t.Errorf( + t.Fatalf( "failed to send taildrop file on %s, err: %s", client.Hostname(), err, @@ -434,7 +363,7 @@ func TestTaildrop(t *testing.T) { "/tmp/", } if _, _, err := client.Execute(command); err != nil { - t.Errorf("failed to get taildrop file on %s, err: %s", client.Hostname(), err) + t.Fatalf("failed to get taildrop file on %s, err: %s", client.Hostname(), err) } for _, peer := range allClients { @@ -454,13 +383,11 @@ func TestTaildrop(t *testing.T) { ) result, _, err := client.Execute(command) - if err != nil { - t.Errorf("failed to execute command to ls taildrop: %s", err) - } + assertNoErrf(t, "failed to execute command to ls taildrop: %s", err) log.Printf("Result for %s: %s\n", peer.Hostname(), result) if fmt.Sprintf("/tmp/file_from_%s\n", peer.Hostname()) != result { - t.Errorf( + t.Fatalf( "taildrop result is not correct %s, wanted %s", result, fmt.Sprintf("/tmp/file_from_%s\n", peer.Hostname()), @@ -469,11 +396,6 @@ func TestTaildrop(t *testing.T) { }) } } - - err = scenario.Shutdown() - if err != nil { - t.Errorf("failed to tear down scenario: %s", err) - } } func TestResolveMagicDNS(t *testing.T) { @@ -481,9 +403,8 @@ func TestResolveMagicDNS(t *testing.T) { t.Parallel() scenario, err := NewScenario() - if err != nil { - t.Errorf("failed to create scenario: %s", err) - } + assertNoErr(t, err) + defer scenario.Shutdown() spec := map[string]int{ // Omit 1.16.2 (-1) because it does not have the FQDN field @@ -492,30 +413,20 @@ func TestResolveMagicDNS(t *testing.T) { } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("magicdns")) - if err != nil { - t.Errorf("failed to create headscale environment: %s", err) - } + assertNoErrHeadscaleEnv(t, err) allClients, err := scenario.ListTailscaleClients() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClients(t, err) err = scenario.WaitForTailscaleSync() - if err != nil { - t.Errorf("failed wait for tailscale clients to be in sync: %s", err) - } + assertNoErrSync(t, err) // Poor mans cache _, err = scenario.ListTailscaleClientsFQDNs() - if err != nil { - t.Errorf("failed to get FQDNs: %s", err) - } + assertNoErrListFQDN(t, err) _, err = scenario.ListTailscaleClientsIPs() - if err != nil { - t.Errorf("failed to get IPs: %s", err) - } + assertNoErrListClientIPs(t, err) for _, client := range allClients { for _, peer := range allClients { @@ -528,7 +439,7 @@ func TestResolveMagicDNS(t *testing.T) { } result, _, err := client.Execute(command) if err != nil { - t.Errorf( + t.Fatalf( "failed to execute resolve/ip command %s from %s: %s", peerFQDN, client.Hostname(), @@ -538,7 +449,7 @@ func TestResolveMagicDNS(t *testing.T) { ips, err := peer.IPs() if err != nil { - t.Errorf( + t.Fatalf( "failed to get ips for %s: %s", peer.Hostname(), err, @@ -547,16 +458,11 @@ func TestResolveMagicDNS(t *testing.T) { for _, ip := range ips { if !strings.Contains(result, ip.String()) { - t.Errorf("ip %s is not found in \n%s\n", ip.String(), result) + t.Fatalf("ip %s is not found in \n%s\n", ip.String(), result) } } } } - - err = scenario.Shutdown() - if err != nil { - t.Errorf("failed to tear down scenario: %s", err) - } } func TestExpireNode(t *testing.T) { @@ -564,33 +470,24 @@ func TestExpireNode(t *testing.T) { t.Parallel() scenario, err := NewScenario() - if err != nil { - t.Errorf("failed to create scenario: %s", err) - } + assertNoErr(t, err) + defer scenario.Shutdown() spec := map[string]int{ "user1": len(TailscaleVersions), } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("expirenode")) - if err != nil { - t.Errorf("failed to create headscale environment: %s", err) - } + assertNoErrHeadscaleEnv(t, err) allClients, err := scenario.ListTailscaleClients() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClients(t, err) allIps, err := scenario.ListTailscaleClientsIPs() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClientIPs(t, err) err = scenario.WaitForTailscaleSync() - if err != nil { - t.Errorf("failed wait for tailscale clients to be in sync: %s", err) - } + assertNoErrSync(t, err) allAddrs := lo.Map(allIps, func(x netip.Addr, index int) string { return x.String() @@ -601,25 +498,25 @@ func TestExpireNode(t *testing.T) { for _, client := range allClients { status, err := client.Status() - assert.NoError(t, err) + assertNoErr(t, err) // Assert that we have the original count - self assert.Len(t, status.Peers(), len(TailscaleVersions)-1) } headscale, err := scenario.Headscale() - assert.NoError(t, err) + assertNoErr(t, err) // TODO(kradalby): This is Headscale specific and would not play nicely // with other implementations of the ControlServer interface result, err := headscale.Execute([]string{ "headscale", "nodes", "expire", "--identifier", "0", "--output", "json", }) - assert.NoError(t, err) + assertNoErr(t, err) var machine v1.Machine err = json.Unmarshal([]byte(result), &machine) - assert.NoError(t, err) + assertNoErr(t, err) time.Sleep(30 * time.Second) @@ -627,7 +524,7 @@ func TestExpireNode(t *testing.T) { // of connected nodes. for _, client := range allClients { status, err := client.Status() - assert.NoError(t, err) + assertNoErr(t, err) for _, peerKey := range status.Peers() { peerStatus := status.Peer[peerKey] @@ -642,9 +539,4 @@ func TestExpireNode(t *testing.T) { assert.Len(t, status.Peers(), len(TailscaleVersions)-2) } } - - err = scenario.Shutdown() - if err != nil { - t.Errorf("failed to tear down scenario: %s", err) - } } diff --git a/integration/hsic/hsic.go b/integration/hsic/hsic.go index d27eb06f..e13b7273 100644 --- a/integration/hsic/hsic.go +++ b/integration/hsic/hsic.go @@ -428,9 +428,9 @@ func (t *HeadscaleInContainer) GetHostname() string { return t.hostname } -// WaitForReady blocks until the Headscale instance is ready to +// WaitForRunning blocks until the Headscale instance is ready to // serve clients. -func (t *HeadscaleInContainer) WaitForReady() error { +func (t *HeadscaleInContainer) WaitForRunning() error { url := t.GetHealthEndpoint() log.Printf("waiting for headscale to be ready at %s", url) diff --git a/integration/scenario.go b/integration/scenario.go index 927d6c80..e39d6c00 100644 --- a/integration/scenario.go +++ b/integration/scenario.go @@ -16,6 +16,7 @@ import ( "github.com/juanfont/headscale/integration/tsic" "github.com/ory/dockertest/v3" "github.com/puzpuzpuz/xsync/v2" + "golang.org/x/sync/errgroup" ) const ( @@ -33,30 +34,33 @@ var ( tailscaleVersions2021 = []string{ "head", "unstable", - "1.40.0", - "1.38.4", - "1.36.2", - "1.34.2", - "1.32.3", - "1.30.2", + "1.48", + "1.46", + "1.44", + "1.42", + "1.40", + "1.38", + "1.36", + "1.34", + "1.32", + "1.30", } tailscaleVersions2019 = []string{ - "1.28.0", - "1.26.2", - "1.24.2", - "1.22.2", - "1.20.4", + "1.28", + "1.26", + "1.24", + "1.22", + "1.20", + "1.18", } // tailscaleVersionsUnavailable = []string{ // // These versions seem to fail when fetching from apt. - // "1.18.2", - // "1.16.2", - // "1.14.6", - // "1.12.4", - // "1.10.2", - // "1.8.7", + // "1.14.6", + // "1.12.4", + // "1.10.2", + // "1.8.7", // }. // TailscaleVersions represents a list of Tailscale versions the suite @@ -79,9 +83,9 @@ var ( type User struct { Clients map[string]TailscaleClient - createWaitGroup sync.WaitGroup - joinWaitGroup sync.WaitGroup - syncWaitGroup sync.WaitGroup + createWaitGroup errgroup.Group + joinWaitGroup errgroup.Group + syncWaitGroup errgroup.Group } // Scenario is a representation of an environment with one ControlServer and @@ -148,7 +152,7 @@ func NewScenario() (*Scenario, error) { // and networks associated with it. // In addition, it will save the logs of the ControlServer to `/tmp/control` in the // environment running the tests. -func (s *Scenario) Shutdown() error { +func (s *Scenario) Shutdown() { s.controlServers.Range(func(_ string, control ControlServer) bool { err := control.Shutdown() if err != nil { @@ -166,21 +170,19 @@ func (s *Scenario) Shutdown() error { log.Printf("removing client %s in user %s", client.Hostname(), userName) err := client.Shutdown() if err != nil { - return fmt.Errorf("failed to tear down client: %w", err) + log.Printf("failed to tear down client: %s", err) } } } if err := s.pool.RemoveNetwork(s.network); err != nil { - return fmt.Errorf("failed to remove network: %w", err) + log.Printf("failed to remove network: %s", err) } // TODO(kradalby): This seem redundant to the previous call // if err := s.network.Close(); err != nil { // return fmt.Errorf("failed to tear down network: %w", err) // } - - return nil } // Users returns the name of all users associated with the Scenario. @@ -213,7 +215,7 @@ func (s *Scenario) Headscale(opts ...hsic.Option) (ControlServer, error) { return nil, fmt.Errorf("failed to create headscale container: %w", err) } - err = headscale.WaitForReady() + err = headscale.WaitForRunning() if err != nil { return nil, fmt.Errorf("failed reach headscale container: %w", err) } @@ -286,17 +288,12 @@ func (s *Scenario) CreateTailscaleNodesInUser( cert := headscale.GetCert() hostname := headscale.GetHostname() - user.createWaitGroup.Add(1) - opts = append(opts, tsic.WithHeadscaleTLS(cert), tsic.WithHeadscaleName(hostname), ) - go func() { - defer user.createWaitGroup.Done() - - // TODO(kradalby): error handle this + user.createWaitGroup.Go(func() error { tsClient, err := tsic.New( s.pool, version, @@ -304,20 +301,30 @@ func (s *Scenario) CreateTailscaleNodesInUser( opts..., ) if err != nil { - // return fmt.Errorf("failed to add tailscale node: %w", err) - log.Printf("failed to create tailscale node: %s", err) + return fmt.Errorf( + "failed to create tailscale (%s) node: %w", + tsClient.Hostname(), + err, + ) } - err = tsClient.WaitForReady() + err = tsClient.WaitForNeedsLogin() if err != nil { - // return fmt.Errorf("failed to add tailscale node: %w", err) - log.Printf("failed to wait for tailscaled: %s", err) + return fmt.Errorf( + "failed to wait for tailscaled (%s) to need login: %w", + tsClient.Hostname(), + err, + ) } user.Clients[tsClient.Hostname()] = tsClient - }() + + return nil + }) + } + if err := user.createWaitGroup.Wait(); err != nil { + return err } - user.createWaitGroup.Wait() return nil } @@ -332,29 +339,20 @@ func (s *Scenario) RunTailscaleUp( ) error { if user, ok := s.users[userStr]; ok { for _, client := range user.Clients { - user.joinWaitGroup.Add(1) - - go func(c TailscaleClient) { - defer user.joinWaitGroup.Done() - - // TODO(kradalby): error handle this - _ = c.Up(loginServer, authKey) - }(client) - - err := client.WaitForReady() - if err != nil { - log.Printf("error waiting for client %s to be ready: %s", client.Hostname(), err) - } + c := client + user.joinWaitGroup.Go(func() error { + return c.Login(loginServer, authKey) + }) } - user.joinWaitGroup.Wait() + if err := user.joinWaitGroup.Wait(); err != nil { + return err + } for _, client := range user.Clients { - err := client.WaitForReady() + err := client.WaitForRunning() if err != nil { - log.Printf("client %s was not ready: %s", client.Hostname(), err) - - return fmt.Errorf("failed to up tailscale node: %w", err) + return fmt.Errorf("%s failed to up tailscale node: %w", client.Hostname(), err) } } @@ -381,18 +379,22 @@ func (s *Scenario) CountTailscale() int { func (s *Scenario) WaitForTailscaleSync() error { tsCount := s.CountTailscale() + return s.WaitForTailscaleSyncWithPeerCount(tsCount - 1) +} + +// WaitForTailscaleSyncWithPeerCount blocks execution until all the TailscaleClient reports +// to have all other TailscaleClients present in their netmap.NetworkMap. +func (s *Scenario) WaitForTailscaleSyncWithPeerCount(peerCount int) error { for _, user := range s.users { for _, client := range user.Clients { - user.syncWaitGroup.Add(1) - - go func(c TailscaleClient) { - defer user.syncWaitGroup.Done() - - // TODO(kradalby): error handle this - _ = c.WaitForPeers(tsCount) - }(client) + c := client + user.syncWaitGroup.Go(func() error { + return c.WaitForPeers(peerCount) + }) + } + if err := user.syncWaitGroup.Wait(); err != nil { + return err } - user.syncWaitGroup.Wait() } return nil @@ -555,18 +557,18 @@ func (s *Scenario) ListTailscaleClientsFQDNs(users ...string) ([]string, error) // WaitForTailscaleLogout blocks execution until all TailscaleClients have // logged out of the ControlServer. -func (s *Scenario) WaitForTailscaleLogout() { +func (s *Scenario) WaitForTailscaleLogout() error { for _, user := range s.users { for _, client := range user.Clients { - user.syncWaitGroup.Add(1) - - go func(c TailscaleClient) { - defer user.syncWaitGroup.Done() - - // TODO(kradalby): error handle this - _ = c.WaitForLogout() - }(client) + c := client + user.syncWaitGroup.Go(func() error { + return c.WaitForLogout() + }) + } + if err := user.syncWaitGroup.Wait(); err != nil { + return err } - user.syncWaitGroup.Wait() } + + return nil } diff --git a/integration/scenario_test.go b/integration/scenario_test.go index 31f69530..59b6a33c 100644 --- a/integration/scenario_test.go +++ b/integration/scenario_test.go @@ -34,44 +34,38 @@ func TestHeadscale(t *testing.T) { user := "test-space" scenario, err := NewScenario() - if err != nil { - t.Errorf("failed to create scenario: %s", err) - } + assertNoErr(t, err) + defer scenario.Shutdown() t.Run("start-headscale", func(t *testing.T) { headscale, err := scenario.Headscale() if err != nil { - t.Errorf("failed to create start headcale: %s", err) + t.Fatalf("failed to create start headcale: %s", err) } - err = headscale.WaitForReady() + err = headscale.WaitForRunning() if err != nil { - t.Errorf("headscale failed to become ready: %s", err) + t.Fatalf("headscale failed to become ready: %s", err) } }) t.Run("create-user", func(t *testing.T) { err := scenario.CreateUser(user) if err != nil { - t.Errorf("failed to create user: %s", err) + t.Fatalf("failed to create user: %s", err) } if _, ok := scenario.users[user]; !ok { - t.Errorf("user is not in scenario") + t.Fatalf("user is not in scenario") } }) t.Run("create-auth-key", func(t *testing.T) { _, err := scenario.CreatePreAuthKey(user, true, false) if err != nil { - t.Errorf("failed to create preauthkey: %s", err) + t.Fatalf("failed to create preauthkey: %s", err) } }) - - err = scenario.Shutdown() - if err != nil { - t.Errorf("failed to tear down scenario: %s", err) - } } // If subtests are parallel, then they will start before setup is run. @@ -85,9 +79,8 @@ func TestCreateTailscale(t *testing.T) { user := "only-create-containers" scenario, err := NewScenario() - if err != nil { - t.Errorf("failed to create scenario: %s", err) - } + assertNoErr(t, err) + defer scenario.Shutdown() scenario.users[user] = &User{ Clients: make(map[string]TailscaleClient), @@ -96,20 +89,15 @@ func TestCreateTailscale(t *testing.T) { t.Run("create-tailscale", func(t *testing.T) { err := scenario.CreateTailscaleNodesInUser(user, "all", 3) if err != nil { - t.Errorf("failed to add tailscale nodes: %s", err) + t.Fatalf("failed to add tailscale nodes: %s", err) } if clients := len(scenario.users[user].Clients); clients != 3 { - t.Errorf("wrong number of tailscale clients: %d != %d", clients, 3) + t.Fatalf("wrong number of tailscale clients: %d != %d", clients, 3) } // TODO(kradalby): Test "all" version logic }) - - err = scenario.Shutdown() - if err != nil { - t.Errorf("failed to tear down scenario: %s", err) - } } // If subtests are parallel, then they will start before setup is run. @@ -127,53 +115,52 @@ func TestTailscaleNodesJoiningHeadcale(t *testing.T) { count := 1 scenario, err := NewScenario() - if err != nil { - t.Errorf("failed to create scenario: %s", err) - } + assertNoErr(t, err) + defer scenario.Shutdown() t.Run("start-headscale", func(t *testing.T) { headscale, err := scenario.Headscale() if err != nil { - t.Errorf("failed to create start headcale: %s", err) + t.Fatalf("failed to create start headcale: %s", err) } - err = headscale.WaitForReady() + err = headscale.WaitForRunning() if err != nil { - t.Errorf("headscale failed to become ready: %s", err) + t.Fatalf("headscale failed to become ready: %s", err) } }) t.Run("create-user", func(t *testing.T) { err := scenario.CreateUser(user) if err != nil { - t.Errorf("failed to create user: %s", err) + t.Fatalf("failed to create user: %s", err) } if _, ok := scenario.users[user]; !ok { - t.Errorf("user is not in scenario") + t.Fatalf("user is not in scenario") } }) t.Run("create-tailscale", func(t *testing.T) { err := scenario.CreateTailscaleNodesInUser(user, "1.30.2", count) if err != nil { - t.Errorf("failed to add tailscale nodes: %s", err) + t.Fatalf("failed to add tailscale nodes: %s", err) } if clients := len(scenario.users[user].Clients); clients != count { - t.Errorf("wrong number of tailscale clients: %d != %d", clients, count) + t.Fatalf("wrong number of tailscale clients: %d != %d", clients, count) } }) t.Run("join-headscale", func(t *testing.T) { key, err := scenario.CreatePreAuthKey(user, true, false) if err != nil { - t.Errorf("failed to create preauthkey: %s", err) + t.Fatalf("failed to create preauthkey: %s", err) } headscale, err := scenario.Headscale() if err != nil { - t.Errorf("failed to create start headcale: %s", err) + t.Fatalf("failed to create start headcale: %s", err) } err = scenario.RunTailscaleUp( @@ -182,23 +169,18 @@ func TestTailscaleNodesJoiningHeadcale(t *testing.T) { key.GetKey(), ) if err != nil { - t.Errorf("failed to login: %s", err) + t.Fatalf("failed to login: %s", err) } }) t.Run("get-ips", func(t *testing.T) { ips, err := scenario.GetIPs(user) if err != nil { - t.Errorf("failed to get tailscale ips: %s", err) + t.Fatalf("failed to get tailscale ips: %s", err) } if len(ips) != count*2 { - t.Errorf("got the wrong amount of tailscale ips, %d != %d", len(ips), count*2) + t.Fatalf("got the wrong amount of tailscale ips, %d != %d", len(ips), count*2) } }) - - err = scenario.Shutdown() - if err != nil { - t.Errorf("failed to tear down scenario: %s", err) - } } diff --git a/integration/ssh_test.go b/integration/ssh_test.go index c8963b19..f89c0253 100644 --- a/integration/ssh_test.go +++ b/integration/ssh_test.go @@ -41,65 +41,79 @@ var retry = func(times int, sleepInterval time.Duration, return result, stderr, err } -func TestSSHOneUserAllToAll(t *testing.T) { - IntegrationSkip(t) - t.Parallel() - +func sshScenario(t *testing.T, policy *policy.ACLPolicy, clientsPerUser int) *Scenario { + t.Helper() scenario, err := NewScenario() - if err != nil { - t.Errorf("failed to create scenario: %s", err) - } + assertNoErr(t, err) spec := map[string]int{ - "user1": len(TailscaleVersions) - 5, + "user1": clientsPerUser, + "user2": clientsPerUser, } err = scenario.CreateHeadscaleEnv(spec, - []tsic.Option{tsic.WithSSH()}, - hsic.WithACLPolicy( - &policy.ACLPolicy{ - Groups: map[string][]string{ - "group:integration-test": {"user1"}, - }, - ACLs: []policy.ACL{ - { - Action: "accept", - Sources: []string{"*"}, - Destinations: []string{"*:*"}, - }, - }, - SSHs: []policy.SSH{ - { - Action: "accept", - Sources: []string{"group:integration-test"}, - Destinations: []string{"group:integration-test"}, - Users: []string{"ssh-it-user"}, - }, - }, - }, - ), + []tsic.Option{ + tsic.WithDockerEntrypoint([]string{ + "/bin/sh", + "-c", + "/bin/sleep 3 ; apk add openssh ; update-ca-certificates ; tailscaled --tun=tsdev", + }), + tsic.WithDockerWorkdir("/"), + }, + hsic.WithACLPolicy(policy), + hsic.WithTestName("ssh"), hsic.WithConfigEnv(map[string]string{ "HEADSCALE_EXPERIMENTAL_FEATURE_SSH": "1", }), ) - if err != nil { - t.Errorf("failed to create headscale environment: %s", err) - } - - allClients, err := scenario.ListTailscaleClients() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErr(t, err) err = scenario.WaitForTailscaleSync() - if err != nil { - t.Errorf("failed wait for tailscale clients to be in sync: %s", err) - } + assertNoErr(t, err) _, err = scenario.ListTailscaleClientsFQDNs() - if err != nil { - t.Errorf("failed to get FQDNs: %s", err) - } + assertNoErr(t, err) + + return scenario +} + +func TestSSHOneUserAllToAll(t *testing.T) { + IntegrationSkip(t) + t.Parallel() + + scenario := sshScenario(t, + &policy.ACLPolicy{ + Groups: map[string][]string{ + "group:integration-test": {"user1"}, + }, + ACLs: []policy.ACL{ + { + Action: "accept", + Sources: []string{"*"}, + Destinations: []string{"*:*"}, + }, + }, + SSHs: []policy.SSH{ + { + Action: "accept", + Sources: []string{"group:integration-test"}, + Destinations: []string{"group:integration-test"}, + Users: []string{"ssh-it-user"}, + }, + }, + }, + len(TailscaleVersions)-5, + ) + defer scenario.Shutdown() + + allClients, err := scenario.ListTailscaleClients() + assertNoErrListClients(t, err) + + err = scenario.WaitForTailscaleSync() + assertNoErrSync(t, err) + + _, err = scenario.ListTailscaleClientsFQDNs() + assertNoErrListFQDN(t, err) for _, client := range allClients { for _, peer := range allClients { @@ -110,78 +124,48 @@ func TestSSHOneUserAllToAll(t *testing.T) { assertSSHHostname(t, client, peer) } } - - err = scenario.Shutdown() - if err != nil { - t.Errorf("failed to tear down scenario: %s", err) - } } func TestSSHMultipleUsersAllToAll(t *testing.T) { IntegrationSkip(t) t.Parallel() - scenario, err := NewScenario() - if err != nil { - t.Errorf("failed to create scenario: %s", err) - } - - spec := map[string]int{ - "user1": len(TailscaleVersions) - 5, - "user2": len(TailscaleVersions) - 5, - } - - err = scenario.CreateHeadscaleEnv(spec, - []tsic.Option{tsic.WithSSH()}, - hsic.WithACLPolicy( - &policy.ACLPolicy{ - Groups: map[string][]string{ - "group:integration-test": {"user1", "user2"}, - }, - ACLs: []policy.ACL{ - { - Action: "accept", - Sources: []string{"*"}, - Destinations: []string{"*:*"}, - }, - }, - SSHs: []policy.SSH{ - { - Action: "accept", - Sources: []string{"group:integration-test"}, - Destinations: []string{"group:integration-test"}, - Users: []string{"ssh-it-user"}, - }, + scenario := sshScenario(t, + &policy.ACLPolicy{ + Groups: map[string][]string{ + "group:integration-test": {"user1", "user2"}, + }, + ACLs: []policy.ACL{ + { + Action: "accept", + Sources: []string{"*"}, + Destinations: []string{"*:*"}, }, }, - ), - hsic.WithConfigEnv(map[string]string{ - "HEADSCALE_EXPERIMENTAL_FEATURE_SSH": "1", - }), + SSHs: []policy.SSH{ + { + Action: "accept", + Sources: []string{"group:integration-test"}, + Destinations: []string{"group:integration-test"}, + Users: []string{"ssh-it-user"}, + }, + }, + }, + len(TailscaleVersions)-5, ) - if err != nil { - t.Errorf("failed to create headscale environment: %s", err) - } + defer scenario.Shutdown() nsOneClients, err := scenario.ListTailscaleClients("user1") - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClients(t, err) nsTwoClients, err := scenario.ListTailscaleClients("user2") - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClients(t, err) err = scenario.WaitForTailscaleSync() - if err != nil { - t.Errorf("failed wait for tailscale clients to be in sync: %s", err) - } + assertNoErrSync(t, err) _, err = scenario.ListTailscaleClientsFQDNs() - if err != nil { - t.Errorf("failed to get FQDNs: %s", err) - } + assertNoErrListFQDN(t, err) testInterUserSSH := func(sourceClients []TailscaleClient, targetClients []TailscaleClient) { for _, client := range sourceClients { @@ -193,66 +177,38 @@ func TestSSHMultipleUsersAllToAll(t *testing.T) { testInterUserSSH(nsOneClients, nsTwoClients) testInterUserSSH(nsTwoClients, nsOneClients) - - err = scenario.Shutdown() - if err != nil { - t.Errorf("failed to tear down scenario: %s", err) - } } func TestSSHNoSSHConfigured(t *testing.T) { IntegrationSkip(t) t.Parallel() - scenario, err := NewScenario() - if err != nil { - t.Errorf("failed to create scenario: %s", err) - } - - spec := map[string]int{ - "user1": len(TailscaleVersions) - 5, - } - - err = scenario.CreateHeadscaleEnv(spec, - []tsic.Option{tsic.WithSSH()}, - hsic.WithACLPolicy( - &policy.ACLPolicy{ - Groups: map[string][]string{ - "group:integration-test": {"user1"}, - }, - ACLs: []policy.ACL{ - { - Action: "accept", - Sources: []string{"*"}, - Destinations: []string{"*:*"}, - }, - }, - SSHs: []policy.SSH{}, + scenario := sshScenario(t, + &policy.ACLPolicy{ + Groups: map[string][]string{ + "group:integration-test": {"user1"}, }, - ), - hsic.WithTestName("sshnoneconfigured"), - hsic.WithConfigEnv(map[string]string{ - "HEADSCALE_EXPERIMENTAL_FEATURE_SSH": "1", - }), + ACLs: []policy.ACL{ + { + Action: "accept", + Sources: []string{"*"}, + Destinations: []string{"*:*"}, + }, + }, + SSHs: []policy.SSH{}, + }, + len(TailscaleVersions)-5, ) - if err != nil { - t.Errorf("failed to create headscale environment: %s", err) - } + defer scenario.Shutdown() allClients, err := scenario.ListTailscaleClients() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClients(t, err) err = scenario.WaitForTailscaleSync() - if err != nil { - t.Errorf("failed wait for tailscale clients to be in sync: %s", err) - } + assertNoErrSync(t, err) _, err = scenario.ListTailscaleClientsFQDNs() - if err != nil { - t.Errorf("failed to get FQDNs: %s", err) - } + assertNoErrListFQDN(t, err) for _, client := range allClients { for _, peer := range allClients { @@ -263,73 +219,45 @@ func TestSSHNoSSHConfigured(t *testing.T) { assertSSHPermissionDenied(t, client, peer) } } - - err = scenario.Shutdown() - if err != nil { - t.Errorf("failed to tear down scenario: %s", err) - } } func TestSSHIsBlockedInACL(t *testing.T) { IntegrationSkip(t) t.Parallel() - scenario, err := NewScenario() - if err != nil { - t.Errorf("failed to create scenario: %s", err) - } - - spec := map[string]int{ - "user1": len(TailscaleVersions) - 5, - } - - err = scenario.CreateHeadscaleEnv(spec, - []tsic.Option{tsic.WithSSH()}, - hsic.WithACLPolicy( - &policy.ACLPolicy{ - Groups: map[string][]string{ - "group:integration-test": {"user1"}, - }, - ACLs: []policy.ACL{ - { - Action: "accept", - Sources: []string{"*"}, - Destinations: []string{"*:80"}, - }, - }, - SSHs: []policy.SSH{ - { - Action: "accept", - Sources: []string{"group:integration-test"}, - Destinations: []string{"group:integration-test"}, - Users: []string{"ssh-it-user"}, - }, + scenario := sshScenario(t, + &policy.ACLPolicy{ + Groups: map[string][]string{ + "group:integration-test": {"user1"}, + }, + ACLs: []policy.ACL{ + { + Action: "accept", + Sources: []string{"*"}, + Destinations: []string{"*:80"}, }, }, - ), - hsic.WithTestName("sshisblockedinacl"), - hsic.WithConfigEnv(map[string]string{ - "HEADSCALE_EXPERIMENTAL_FEATURE_SSH": "1", - }), + SSHs: []policy.SSH{ + { + Action: "accept", + Sources: []string{"group:integration-test"}, + Destinations: []string{"group:integration-test"}, + Users: []string{"ssh-it-user"}, + }, + }, + }, + len(TailscaleVersions)-5, ) - if err != nil { - t.Errorf("failed to create headscale environment: %s", err) - } + defer scenario.Shutdown() allClients, err := scenario.ListTailscaleClients() - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + assertNoErrListClients(t, err) err = scenario.WaitForTailscaleSync() - if err != nil { - t.Errorf("failed wait for tailscale clients to be in sync: %s", err) - } + assertNoErrSync(t, err) _, err = scenario.ListTailscaleClientsFQDNs() - if err != nil { - t.Errorf("failed to get FQDNs: %s", err) - } + assertNoErrListFQDN(t, err) for _, client := range allClients { for _, peer := range allClients { @@ -340,86 +268,55 @@ func TestSSHIsBlockedInACL(t *testing.T) { assertSSHTimeout(t, client, peer) } } - - err = scenario.Shutdown() - if err != nil { - t.Errorf("failed to tear down scenario: %s", err) - } } func TestSSUserOnlyIsolation(t *testing.T) { IntegrationSkip(t) t.Parallel() - scenario, err := NewScenario() - if err != nil { - t.Errorf("failed to create scenario: %s", err) - } - - spec := map[string]int{ - "useracl1": len(TailscaleVersions) - 5, - "useracl2": len(TailscaleVersions) - 5, - } - - err = scenario.CreateHeadscaleEnv(spec, - []tsic.Option{tsic.WithSSH()}, - hsic.WithACLPolicy( - &policy.ACLPolicy{ - Groups: map[string][]string{ - "group:ssh1": {"useracl1"}, - "group:ssh2": {"useracl2"}, - }, - ACLs: []policy.ACL{ - { - Action: "accept", - Sources: []string{"*"}, - Destinations: []string{"*:*"}, - }, - }, - SSHs: []policy.SSH{ - { - Action: "accept", - Sources: []string{"group:ssh1"}, - Destinations: []string{"group:ssh1"}, - Users: []string{"ssh-it-user"}, - }, - { - Action: "accept", - Sources: []string{"group:ssh2"}, - Destinations: []string{"group:ssh2"}, - Users: []string{"ssh-it-user"}, - }, + scenario := sshScenario(t, + &policy.ACLPolicy{ + Groups: map[string][]string{ + "group:ssh1": {"user1"}, + "group:ssh2": {"user2"}, + }, + ACLs: []policy.ACL{ + { + Action: "accept", + Sources: []string{"*"}, + Destinations: []string{"*:*"}, }, }, - ), - hsic.WithTestName("sshtwouseraclblock"), - hsic.WithConfigEnv(map[string]string{ - "HEADSCALE_EXPERIMENTAL_FEATURE_SSH": "1", - }), + SSHs: []policy.SSH{ + { + Action: "accept", + Sources: []string{"group:ssh1"}, + Destinations: []string{"group:ssh1"}, + Users: []string{"ssh-it-user"}, + }, + { + Action: "accept", + Sources: []string{"group:ssh2"}, + Destinations: []string{"group:ssh2"}, + Users: []string{"ssh-it-user"}, + }, + }, + }, + len(TailscaleVersions)-5, ) - if err != nil { - t.Errorf("failed to create headscale environment: %s", err) - } + defer scenario.Shutdown() - ssh1Clients, err := scenario.ListTailscaleClients("useracl1") - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + ssh1Clients, err := scenario.ListTailscaleClients("user1") + assertNoErrListClients(t, err) - ssh2Clients, err := scenario.ListTailscaleClients("useracl2") - if err != nil { - t.Errorf("failed to get clients: %s", err) - } + ssh2Clients, err := scenario.ListTailscaleClients("user2") + assertNoErrListClients(t, err) err = scenario.WaitForTailscaleSync() - if err != nil { - t.Errorf("failed wait for tailscale clients to be in sync: %s", err) - } + assertNoErrSync(t, err) _, err = scenario.ListTailscaleClientsFQDNs() - if err != nil { - t.Errorf("failed to get FQDNs: %s", err) - } + assertNoErrListFQDN(t, err) for _, client := range ssh1Clients { for _, peer := range ssh2Clients { @@ -460,11 +357,6 @@ func TestSSUserOnlyIsolation(t *testing.T) { assertSSHHostname(t, client, peer) } } - - err = scenario.Shutdown() - if err != nil { - t.Errorf("failed to tear down scenario: %s", err) - } } func doSSH(t *testing.T, client TailscaleClient, peer TailscaleClient) (string, string, error) { @@ -487,7 +379,7 @@ func assertSSHHostname(t *testing.T, client TailscaleClient, peer TailscaleClien t.Helper() result, _, err := doSSH(t, client, peer) - assert.NoError(t, err) + assertNoErr(t, err) assert.Contains(t, peer.ID(), strings.ReplaceAll(result, "\n", "")) } @@ -507,7 +399,7 @@ func assertSSHTimeout(t *testing.T, client TailscaleClient, peer TailscaleClient t.Helper() result, stderr, err := doSSH(t, client, peer) - assert.NoError(t, err) + assertNoErr(t, err) assert.Empty(t, result) diff --git a/integration/tailscale.go b/integration/tailscale.go index 166b851f..46c87c47 100644 --- a/integration/tailscale.go +++ b/integration/tailscale.go @@ -14,14 +14,18 @@ type TailscaleClient interface { Hostname() string Shutdown() error Version() string - Execute(command []string, options ...dockertestutil.ExecuteCommandOption) (string, string, error) - Up(loginServer, authKey string) error - UpWithLoginURL(loginServer string) (*url.URL, error) + Execute( + command []string, + options ...dockertestutil.ExecuteCommandOption, + ) (string, string, error) + Login(loginServer, authKey string) error + LoginWithURL(loginServer string) (*url.URL, error) Logout() error IPs() ([]netip.Addr, error) FQDN() (string, error) Status() (*ipnstate.Status, error) - WaitForReady() error + WaitForNeedsLogin() error + WaitForRunning() error WaitForLogout() error WaitForPeers(expected int) error Ping(hostnameOrIP string, opts ...tsic.PingOption) error diff --git a/integration/tsic/tsic.go b/integration/tsic/tsic.go index ffc7e0a9..33b343d2 100644 --- a/integration/tsic/tsic.go +++ b/integration/tsic/tsic.go @@ -34,9 +34,14 @@ var ( errTailscaleWrongPeerCount = errors.New("wrong peer count") errTailscaleCannotUpWithoutAuthkey = errors.New("cannot up without authkey") errTailscaleNotConnected = errors.New("tailscale not connected") + errTailscaledNotReadyForLogin = errors.New("tailscaled not ready for login") errTailscaleNotLoggedOut = errors.New("tailscale not logged out") ) +func errTailscaleStatus(hostname string, err error) error { + return fmt.Errorf("%s failed to fetch tailscale status: %w", hostname, err) +} + // TailscaleInContainer is an implementation of TailscaleClient which // sets up a Tailscale instance inside a container. type TailscaleInContainer struct { @@ -165,7 +170,7 @@ func New( network: network, withEntrypoint: []string{ - "/bin/bash", + "/bin/sh", "-c", "/bin/sleep 3 ; update-ca-certificates ; tailscaled --tun=tsdev", }, @@ -204,16 +209,48 @@ func New( return nil, err } - container, err := pool.BuildAndRunWithBuildOptions( - createTailscaleBuildOptions(version), - tailscaleOptions, - dockertestutil.DockerRestartPolicy, - dockertestutil.DockerAllowLocalIPv6, - dockertestutil.DockerAllowNetworkAdministration, - ) + var container *dockertest.Resource + switch version { + case "head": + buildOptions := &dockertest.BuildOptions{ + Dockerfile: "Dockerfile.tailscale-HEAD", + ContextDir: dockerContextPath, + BuildArgs: []docker.BuildArg{}, + } + + container, err = pool.BuildAndRunWithBuildOptions( + buildOptions, + tailscaleOptions, + dockertestutil.DockerRestartPolicy, + dockertestutil.DockerAllowLocalIPv6, + dockertestutil.DockerAllowNetworkAdministration, + ) + case "unstable": + tailscaleOptions.Repository = "tailscale/tailscale" + tailscaleOptions.Tag = version + + container, err = pool.RunWithOptions( + tailscaleOptions, + dockertestutil.DockerRestartPolicy, + dockertestutil.DockerAllowLocalIPv6, + dockertestutil.DockerAllowNetworkAdministration, + ) + default: + tailscaleOptions.Repository = "tailscale/tailscale" + tailscaleOptions.Tag = "v" + version + + container, err = pool.RunWithOptions( + tailscaleOptions, + dockertestutil.DockerRestartPolicy, + dockertestutil.DockerAllowLocalIPv6, + dockertestutil.DockerAllowNetworkAdministration, + ) + } + if err != nil { return nil, fmt.Errorf( - "could not start tailscale container (version: %s): %w", + "%s could not start tailscale container (version: %s): %w", + hostname, version, err, ) @@ -270,7 +307,7 @@ func (t *TailscaleInContainer) Execute( options..., ) if err != nil { - log.Printf("command stderr: %s\n", stderr) + // log.Printf("command stderr: %s\n", stderr) if stdout != "" { log.Printf("command stdout: %s\n", stdout) @@ -288,18 +325,15 @@ func (t *TailscaleInContainer) Execute( // Up runs the login routine on the given Tailscale instance. // This login mechanism uses the authorised key for authentication. -func (t *TailscaleInContainer) Up( +func (t *TailscaleInContainer) Login( loginServer, authKey string, ) error { command := []string{ "tailscale", "up", - "-login-server", - loginServer, - "--authkey", - authKey, - "--hostname", - t.hostname, + "--login-server=" + loginServer, + "--authkey=" + authKey, + "--hostname=" + t.hostname, } if t.withSSH { @@ -313,7 +347,12 @@ func (t *TailscaleInContainer) Up( } if _, _, err := t.Execute(command); err != nil { - return fmt.Errorf("failed to join tailscale client: %w", err) + return fmt.Errorf( + "%s failed to join tailscale client (%s): %w", + t.hostname, + strings.Join(command, " "), + err, + ) } return nil @@ -321,16 +360,14 @@ func (t *TailscaleInContainer) Up( // Up runs the login routine on the given Tailscale instance. // This login mechanism uses web + command line flow for authentication. -func (t *TailscaleInContainer) UpWithLoginURL( +func (t *TailscaleInContainer) LoginWithURL( loginServer string, ) (*url.URL, error) { command := []string{ "tailscale", "up", - "-login-server", - loginServer, - "--hostname", - t.hostname, + "--login-server=" + loginServer, + "--hostname=" + t.hostname, } _, stderr, err := t.Execute(command) @@ -378,7 +415,7 @@ func (t *TailscaleInContainer) IPs() ([]netip.Addr, error) { result, _, err := t.Execute(command) if err != nil { - return []netip.Addr{}, fmt.Errorf("failed to join tailscale client: %w", err) + return []netip.Addr{}, fmt.Errorf("%s failed to join tailscale client: %w", t.hostname, err) } for _, address := range strings.Split(result, "\n") { @@ -432,19 +469,37 @@ func (t *TailscaleInContainer) FQDN() (string, error) { return status.Self.DNSName, nil } -// WaitForReady blocks until the Tailscale (tailscaled) instance is ready -// to login or be used. -func (t *TailscaleInContainer) WaitForReady() error { +// WaitForNeedsLogin blocks until the Tailscale (tailscaled) instance has +// started and needs to be logged into. +func (t *TailscaleInContainer) WaitForNeedsLogin() error { return t.pool.Retry(func() error { status, err := t.Status() if err != nil { - return fmt.Errorf("failed to fetch tailscale status: %w", err) + return errTailscaleStatus(t.hostname, err) } - if status.CurrentTailnet != nil { + // ipnstate.Status.CurrentTailnet was added in Tailscale 1.22.0 + // https://github.com/tailscale/tailscale/pull/3865 + // + // Before that, we can check the BackendState to see if the + // tailscaled daemon is connected to the control system. + if status.BackendState == "NeedsLogin" { return nil } + return errTailscaledNotReadyForLogin + }) +} + +// WaitForRunning blocks until the Tailscale (tailscaled) instance is logged in +// and ready to be used. +func (t *TailscaleInContainer) WaitForRunning() error { + return t.pool.Retry(func() error { + status, err := t.Status() + if err != nil { + return errTailscaleStatus(t.hostname, err) + } + // ipnstate.Status.CurrentTailnet was added in Tailscale 1.22.0 // https://github.com/tailscale/tailscale/pull/3865 // @@ -460,10 +515,10 @@ func (t *TailscaleInContainer) WaitForReady() error { // WaitForLogout blocks until the Tailscale instance has logged out. func (t *TailscaleInContainer) WaitForLogout() error { - return t.pool.Retry(func() error { + return fmt.Errorf("%s err: %w", t.hostname, t.pool.Retry(func() error { status, err := t.Status() if err != nil { - return fmt.Errorf("failed to fetch tailscale status: %w", err) + return errTailscaleStatus(t.hostname, err) } if status.CurrentTailnet == nil { @@ -471,7 +526,7 @@ func (t *TailscaleInContainer) WaitForLogout() error { } return errTailscaleNotLoggedOut - }) + })) } // WaitForPeers blocks until N number of peers is present in the @@ -480,11 +535,17 @@ func (t *TailscaleInContainer) WaitForPeers(expected int) error { return t.pool.Retry(func() error { status, err := t.Status() if err != nil { - return fmt.Errorf("failed to fetch tailscale status: %w", err) + return errTailscaleStatus(t.hostname, err) } if peers := status.Peers(); len(peers) != expected { - return errTailscaleWrongPeerCount + return fmt.Errorf( + "%s err: %w expected %d, got %d", + t.hostname, + errTailscaleWrongPeerCount, + expected, + len(peers), + ) } return nil @@ -683,47 +744,3 @@ func (t *TailscaleInContainer) Curl(url string, opts ...CurlOption) (string, err func (t *TailscaleInContainer) WriteFile(path string, data []byte) error { return integrationutil.WriteFileToContainer(t.pool, t.container, path, data) } - -func createTailscaleBuildOptions(version string) *dockertest.BuildOptions { - var tailscaleBuildOptions *dockertest.BuildOptions - switch version { - case "head": - tailscaleBuildOptions = &dockertest.BuildOptions{ - Dockerfile: "Dockerfile.tailscale-HEAD", - ContextDir: dockerContextPath, - BuildArgs: []docker.BuildArg{}, - } - case "unstable": - tailscaleBuildOptions = &dockertest.BuildOptions{ - Dockerfile: "Dockerfile.tailscale", - ContextDir: dockerContextPath, - BuildArgs: []docker.BuildArg{ - { - Name: "TAILSCALE_VERSION", - Value: "*", // Installs the latest version https://askubuntu.com/a/824926 - }, - { - Name: "TAILSCALE_CHANNEL", - Value: "unstable", - }, - }, - } - default: - tailscaleBuildOptions = &dockertest.BuildOptions{ - Dockerfile: "Dockerfile.tailscale", - ContextDir: dockerContextPath, - BuildArgs: []docker.BuildArg{ - { - Name: "TAILSCALE_VERSION", - Value: version, - }, - { - Name: "TAILSCALE_CHANNEL", - Value: "stable", - }, - }, - } - } - - return tailscaleBuildOptions -} diff --git a/integration/utils.go b/integration/utils.go index 2ed3e3e7..7cea2176 100644 --- a/integration/utils.go +++ b/integration/utils.go @@ -12,6 +12,53 @@ const ( derpPingCount = 10 ) +func assertNoErr(t *testing.T, err error) { + t.Helper() + assertNoErrf(t, "unexpected error: %s", err) +} + +func assertNoErrf(t *testing.T, msg string, err error) { + t.Helper() + if err != nil { + t.Fatalf(msg, err) + } +} + +func assertNoErrHeadscaleEnv(t *testing.T, err error) { + t.Helper() + assertNoErrf(t, "failed to create headscale environment: %s", err) +} + +func assertNoErrGetHeadscale(t *testing.T, err error) { + t.Helper() + assertNoErrf(t, "failed to get headscale: %s", err) +} + +func assertNoErrListClients(t *testing.T, err error) { + t.Helper() + assertNoErrf(t, "failed to list clients: %s", err) +} + +func assertNoErrListClientIPs(t *testing.T, err error) { + t.Helper() + assertNoErrf(t, "failed to get client IPs: %s", err) +} + +func assertNoErrSync(t *testing.T, err error) { + t.Helper() + assertNoErrf(t, "failed to have all clients sync up: %s", err) +} + +func assertNoErrListFQDN(t *testing.T, err error) { + t.Helper() + assertNoErrf(t, "failed to list FQDNs: %s", err) +} + +func assertNoErrLogout(t *testing.T, err error) { + t.Helper() + assertNoErrf(t, "failed to log out tailscale nodes: %s", err) +} + func pingAllHelper(t *testing.T, clients []TailscaleClient, addrs []string) int { t.Helper() success := 0 @@ -20,7 +67,7 @@ func pingAllHelper(t *testing.T, clients []TailscaleClient, addrs []string) int for _, addr := range addrs { err := client.Ping(addr) if err != nil { - t.Errorf("failed to ping %s from %s: %s", addr, client.Hostname(), err) + t.Fatalf("failed to ping %s from %s: %s", addr, client.Hostname(), err) } else { success++ } @@ -47,7 +94,7 @@ func pingDerpAllHelper(t *testing.T, clients []TailscaleClient, addrs []string) tsic.WithPingUntilDirect(false), ) if err != nil { - t.Errorf("failed to ping %s from %s: %s", addr, client.Hostname(), err) + t.Fatalf("failed to ping %s from %s: %s", addr, client.Hostname(), err) } else { success++ }