mirror of
https://github.com/juanfont/headscale.git
synced 2025-07-21 06:21:17 -04:00
cmd/hi: fixes and qol (#2649)
This commit is contained in:
parent
ea7376f522
commit
afc11e1f0c
20
.github/workflows/build.yml
vendored
20
.github/workflows/build.yml
vendored
@ -17,12 +17,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions: write-all
|
permissions: write-all
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
uses: dorny/paths-filter@v3
|
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
files:
|
files:
|
||||||
@ -31,9 +31,9 @@ jobs:
|
|||||||
- '**/*.go'
|
- '**/*.go'
|
||||||
- 'integration_test/'
|
- 'integration_test/'
|
||||||
- 'config-example.yaml'
|
- 'config-example.yaml'
|
||||||
- uses: nixbuild/nix-quick-install-action@master
|
- uses: nixbuild/nix-quick-install-action@889f3180bb5f064ee9e3201428d04ae9e41d54ad # v31
|
||||||
if: steps.changed-files.outputs.files == 'true'
|
if: steps.changed-files.outputs.files == 'true'
|
||||||
- uses: nix-community/cache-nix-action@main
|
- uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3
|
||||||
if: steps.changed-files.outputs.files == 'true'
|
if: steps.changed-files.outputs.files == 'true'
|
||||||
with:
|
with:
|
||||||
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
||||||
@ -55,7 +55,7 @@ jobs:
|
|||||||
exit $BUILD_STATUS
|
exit $BUILD_STATUS
|
||||||
|
|
||||||
- name: Nix gosum diverging
|
- name: Nix gosum diverging
|
||||||
uses: actions/github-script@v6
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
if: failure() && steps.build.outcome == 'failure'
|
if: failure() && steps.build.outcome == 'failure'
|
||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||||
@ -67,7 +67,7 @@ jobs:
|
|||||||
body: 'Nix build failed with wrong gosum, please update "vendorSha256" (${{ steps.build.outputs.OLD_HASH }}) for the "headscale" package in flake.nix with the new SHA: ${{ steps.build.outputs.NEW_HASH }}'
|
body: 'Nix build failed with wrong gosum, please update "vendorSha256" (${{ steps.build.outputs.OLD_HASH }}) for the "headscale" package in flake.nix with the new SHA: ${{ steps.build.outputs.NEW_HASH }}'
|
||||||
})
|
})
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
if: steps.changed-files.outputs.files == 'true'
|
if: steps.changed-files.outputs.files == 'true'
|
||||||
with:
|
with:
|
||||||
name: headscale-linux
|
name: headscale-linux
|
||||||
@ -86,16 +86,16 @@ jobs:
|
|||||||
- "GOARCH=arm64 GOOS=darwin"
|
- "GOARCH=arm64 GOOS=darwin"
|
||||||
- "GOARCH=amd64 GOOS=darwin"
|
- "GOARCH=amd64 GOOS=darwin"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: nixbuild/nix-quick-install-action@master
|
- uses: nixbuild/nix-quick-install-action@889f3180bb5f064ee9e3201428d04ae9e41d54ad # v31
|
||||||
- uses: nix-community/cache-nix-action@main
|
- uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3
|
||||||
with:
|
with:
|
||||||
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
||||||
restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }}
|
restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }}
|
||||||
|
|
||||||
- name: Run go cross compile
|
- name: Run go cross compile
|
||||||
run: env ${{ matrix.env }} nix develop --command -- go build -o "headscale" ./cmd/headscale
|
run: env ${{ matrix.env }} nix develop --command -- go build -o "headscale" ./cmd/headscale
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: "headscale-${{ matrix.env }}"
|
name: "headscale-${{ matrix.env }}"
|
||||||
path: "headscale"
|
path: "headscale"
|
||||||
|
8
.github/workflows/check-tests.yaml
vendored
8
.github/workflows/check-tests.yaml
vendored
@ -10,12 +10,12 @@ jobs:
|
|||||||
check-tests:
|
check-tests:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
uses: dorny/paths-filter@v3
|
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
files:
|
files:
|
||||||
@ -24,9 +24,9 @@ jobs:
|
|||||||
- '**/*.go'
|
- '**/*.go'
|
||||||
- 'integration_test/'
|
- 'integration_test/'
|
||||||
- 'config-example.yaml'
|
- 'config-example.yaml'
|
||||||
- uses: nixbuild/nix-quick-install-action@master
|
- uses: nixbuild/nix-quick-install-action@889f3180bb5f064ee9e3201428d04ae9e41d54ad # v31
|
||||||
if: steps.changed-files.outputs.files == 'true'
|
if: steps.changed-files.outputs.files == 'true'
|
||||||
- uses: nix-community/cache-nix-action@main
|
- uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3
|
||||||
if: steps.changed-files.outputs.files == 'true'
|
if: steps.changed-files.outputs.files == 'true'
|
||||||
with:
|
with:
|
||||||
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
||||||
|
6
.github/workflows/docs-deploy.yml
vendored
6
.github/workflows/docs-deploy.yml
vendored
@ -21,15 +21,15 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install python
|
- name: Install python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
- name: Setup cache
|
- name: Setup cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||||
with:
|
with:
|
||||||
key: ${{ github.ref }}
|
key: ${{ github.ref }}
|
||||||
path: .cache
|
path: .cache
|
||||||
|
6
.github/workflows/docs-test.yml
vendored
6
.github/workflows/docs-test.yml
vendored
@ -11,13 +11,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Install python
|
- name: Install python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
- name: Setup cache
|
- name: Setup cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||||
with:
|
with:
|
||||||
key: ${{ github.ref }}
|
key: ${{ github.ref }}
|
||||||
path: .cache
|
path: .cache
|
||||||
|
4
.github/workflows/gh-actions-updater.yaml
vendored
4
.github/workflows/gh-actions-updater.yaml
vendored
@ -11,13 +11,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
# [Required] Access token with `workflow` scope.
|
# [Required] Access token with `workflow` scope.
|
||||||
token: ${{ secrets.WORKFLOW_SECRET }}
|
token: ${{ secrets.WORKFLOW_SECRET }}
|
||||||
|
|
||||||
- name: Run GitHub Actions Version Updater
|
- name: Run GitHub Actions Version Updater
|
||||||
uses: saadmk11/github-actions-version-updater@v0.8.1
|
uses: saadmk11/github-actions-version-updater@64be81ba69383f81f2be476703ea6570c4c8686e # v0.8.1
|
||||||
with:
|
with:
|
||||||
# [Required] Access token with `workflow` scope.
|
# [Required] Access token with `workflow` scope.
|
||||||
token: ${{ secrets.WORKFLOW_SECRET }}
|
token: ${{ secrets.WORKFLOW_SECRET }}
|
||||||
|
22
.github/workflows/lint.yml
vendored
22
.github/workflows/lint.yml
vendored
@ -10,12 +10,12 @@ jobs:
|
|||||||
golangci-lint:
|
golangci-lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
uses: dorny/paths-filter@v3
|
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
files:
|
files:
|
||||||
@ -24,9 +24,9 @@ jobs:
|
|||||||
- '**/*.go'
|
- '**/*.go'
|
||||||
- 'integration_test/'
|
- 'integration_test/'
|
||||||
- 'config-example.yaml'
|
- 'config-example.yaml'
|
||||||
- uses: nixbuild/nix-quick-install-action@master
|
- uses: nixbuild/nix-quick-install-action@889f3180bb5f064ee9e3201428d04ae9e41d54ad # v31
|
||||||
if: steps.changed-files.outputs.files == 'true'
|
if: steps.changed-files.outputs.files == 'true'
|
||||||
- uses: nix-community/cache-nix-action@main
|
- uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3
|
||||||
if: steps.changed-files.outputs.files == 'true'
|
if: steps.changed-files.outputs.files == 'true'
|
||||||
with:
|
with:
|
||||||
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
||||||
@ -39,12 +39,12 @@ jobs:
|
|||||||
prettier-lint:
|
prettier-lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
uses: dorny/paths-filter@v3
|
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
files:
|
files:
|
||||||
@ -58,9 +58,9 @@ jobs:
|
|||||||
- '**/*.css'
|
- '**/*.css'
|
||||||
- '**/*.scss'
|
- '**/*.scss'
|
||||||
- '**/*.html'
|
- '**/*.html'
|
||||||
- uses: nixbuild/nix-quick-install-action@master
|
- uses: nixbuild/nix-quick-install-action@889f3180bb5f064ee9e3201428d04ae9e41d54ad # v31
|
||||||
if: steps.changed-files.outputs.files == 'true'
|
if: steps.changed-files.outputs.files == 'true'
|
||||||
- uses: nix-community/cache-nix-action@main
|
- uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3
|
||||||
if: steps.changed-files.outputs.files == 'true'
|
if: steps.changed-files.outputs.files == 'true'
|
||||||
with:
|
with:
|
||||||
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
||||||
@ -73,9 +73,9 @@ jobs:
|
|||||||
proto-lint:
|
proto-lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: nixbuild/nix-quick-install-action@master
|
- uses: nixbuild/nix-quick-install-action@889f3180bb5f064ee9e3201428d04ae9e41d54ad # v31
|
||||||
- uses: nix-community/cache-nix-action@main
|
- uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3
|
||||||
with:
|
with:
|
||||||
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
||||||
restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }}
|
restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }}
|
||||||
|
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@ -13,25 +13,25 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to GHCR
|
- name: Login to GHCR
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- uses: nixbuild/nix-quick-install-action@master
|
- uses: nixbuild/nix-quick-install-action@889f3180bb5f064ee9e3201428d04ae9e41d54ad # v31
|
||||||
- uses: nix-community/cache-nix-action@main
|
- uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3
|
||||||
with:
|
with:
|
||||||
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
||||||
restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }}
|
restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }}
|
||||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9
|
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
|
||||||
with:
|
with:
|
||||||
days-before-issue-stale: 90
|
days-before-issue-stale: 90
|
||||||
days-before-issue-close: 7
|
days-before-issue-close: 7
|
||||||
|
21
.github/workflows/test-integration.yaml
vendored
21
.github/workflows/test-integration.yaml
vendored
@ -92,12 +92,12 @@ jobs:
|
|||||||
# that triggered the build.
|
# that triggered the build.
|
||||||
HAS_TAILSCALE_SECRET: ${{ secrets.TS_OAUTH_CLIENT_ID }}
|
HAS_TAILSCALE_SECRET: ${{ secrets.TS_OAUTH_CLIENT_ID }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
uses: dorny/paths-filter@v3
|
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
files:
|
files:
|
||||||
@ -108,7 +108,7 @@ jobs:
|
|||||||
- 'config-example.yaml'
|
- 'config-example.yaml'
|
||||||
- name: Tailscale
|
- name: Tailscale
|
||||||
if: ${{ env.HAS_TAILSCALE_SECRET }}
|
if: ${{ env.HAS_TAILSCALE_SECRET }}
|
||||||
uses: tailscale/github-action@v2
|
uses: tailscale/github-action@6986d2c82a91fbac2949fe01f5bab95cf21b5102 # v3.2.2
|
||||||
with:
|
with:
|
||||||
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
|
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
|
||||||
oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
|
oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
|
||||||
@ -116,18 +116,15 @@ jobs:
|
|||||||
- name: Setup SSH server for Actor
|
- name: Setup SSH server for Actor
|
||||||
if: ${{ env.HAS_TAILSCALE_SECRET }}
|
if: ${{ env.HAS_TAILSCALE_SECRET }}
|
||||||
uses: alexellis/setup-sshd-actor@master
|
uses: alexellis/setup-sshd-actor@master
|
||||||
- uses: nixbuild/nix-quick-install-action@master
|
- uses: nixbuild/nix-quick-install-action@889f3180bb5f064ee9e3201428d04ae9e41d54ad # v31
|
||||||
if: steps.changed-files.outputs.files == 'true'
|
if: steps.changed-files.outputs.files == 'true'
|
||||||
- uses: nix-community/cache-nix-action@main
|
- uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3
|
||||||
if: steps.changed-files.outputs.files == 'true'
|
if: steps.changed-files.outputs.files == 'true'
|
||||||
with:
|
with:
|
||||||
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
||||||
restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }}
|
restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }}
|
||||||
- uses: satackey/action-docker-layer-caching@main
|
|
||||||
if: steps.changed-files.outputs.files == 'true'
|
|
||||||
continue-on-error: true
|
|
||||||
- name: Run Integration Test
|
- name: Run Integration Test
|
||||||
uses: Wandalen/wretry.action@master
|
uses: Wandalen/wretry.action@e68c23e6309f2871ca8ae4763e7629b9c258e1ea # v3.8.0
|
||||||
if: steps.changed-files.outputs.files == 'true'
|
if: steps.changed-files.outputs.files == 'true'
|
||||||
with:
|
with:
|
||||||
# Our integration tests are started like a thundering herd, often
|
# Our integration tests are started like a thundering herd, often
|
||||||
@ -142,15 +139,15 @@ jobs:
|
|||||||
attempt_delay: 300000 # 5 min
|
attempt_delay: 300000 # 5 min
|
||||||
attempt_limit: 10
|
attempt_limit: 10
|
||||||
command: |
|
command: |
|
||||||
nix develop --command -- go run ./cmd/hi run "^${{ matrix.test }}$" \
|
nix develop --command -- hi run "^${{ matrix.test }}$" \
|
||||||
--timeout=120m \
|
--timeout=120m \
|
||||||
--postgres=${{ matrix.database == 'postgres' && 'true' || 'false' }}
|
--postgres=${{ matrix.database == 'postgres' && 'true' || 'false' }}
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
if: always() && steps.changed-files.outputs.files == 'true'
|
if: always() && steps.changed-files.outputs.files == 'true'
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.test }}-${{matrix.database}}-logs
|
name: ${{ matrix.test }}-${{matrix.database}}-logs
|
||||||
path: "control_logs/*/*.log"
|
path: "control_logs/*/*.log"
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
if: always() && steps.changed-files.outputs.files == 'true'
|
if: always() && steps.changed-files.outputs.files == 'true'
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.test }}-${{matrix.database}}-archives
|
name: ${{ matrix.test }}-${{matrix.database}}-archives
|
||||||
|
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
@ -11,13 +11,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
uses: dorny/paths-filter@v3
|
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
files:
|
files:
|
||||||
@ -27,9 +27,9 @@ jobs:
|
|||||||
- 'integration_test/'
|
- 'integration_test/'
|
||||||
- 'config-example.yaml'
|
- 'config-example.yaml'
|
||||||
|
|
||||||
- uses: nixbuild/nix-quick-install-action@master
|
- uses: nixbuild/nix-quick-install-action@889f3180bb5f064ee9e3201428d04ae9e41d54ad # v31
|
||||||
if: steps.changed-files.outputs.files == 'true'
|
if: steps.changed-files.outputs.files == 'true'
|
||||||
- uses: nix-community/cache-nix-action@main
|
- uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3
|
||||||
if: steps.changed-files.outputs.files == 'true'
|
if: steps.changed-files.outputs.files == 'true'
|
||||||
with:
|
with:
|
||||||
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
||||||
|
6
.github/workflows/update-flake.yml
vendored
6
.github/workflows/update-flake.yml
vendored
@ -10,10 +10,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Install Nix
|
- name: Install Nix
|
||||||
uses: DeterminateSystems/nix-installer-action@main
|
uses: DeterminateSystems/nix-installer-action@21a544727d0c62386e78b4befe52d19ad12692e3 # v17
|
||||||
- name: Update flake.lock
|
- name: Update flake.lock
|
||||||
uses: DeterminateSystems/update-flake-lock@main
|
uses: DeterminateSystems/update-flake-lock@428c2b58a4b7414dabd372acb6a03dba1084d3ab # v25
|
||||||
with:
|
with:
|
||||||
pr-title: "Update flake.lock"
|
pr-title: "Update flake.lock"
|
||||||
|
147
Makefile
147
Makefile
@ -1,53 +1,130 @@
|
|||||||
# Calculate version
|
# Headscale Makefile
|
||||||
version ?= $(shell git describe --always --tags --dirty)
|
# Modern Makefile following best practices
|
||||||
|
|
||||||
rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d))
|
# Version calculation
|
||||||
|
VERSION ?= $(shell git describe --always --tags --dirty)
|
||||||
|
|
||||||
# Determine if OS supports pie
|
# Build configuration
|
||||||
GOOS ?= $(shell uname | tr '[:upper:]' '[:lower:]')
|
GOOS ?= $(shell uname | tr '[:upper:]' '[:lower:]')
|
||||||
ifeq ($(filter $(GOOS), openbsd netbsd soloaris plan9), )
|
ifeq ($(filter $(GOOS), openbsd netbsd solaris plan9), )
|
||||||
pieflags = -buildmode=pie
|
PIE_FLAGS = -buildmode=pie
|
||||||
else
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# GO_SOURCES = $(wildcard *.go)
|
# Tool availability check with nix warning
|
||||||
# PROTO_SOURCES = $(wildcard **/*.proto)
|
define check_tool
|
||||||
GO_SOURCES = $(call rwildcard,,*.go)
|
@command -v $(1) >/dev/null 2>&1 || { \
|
||||||
PROTO_SOURCES = $(call rwildcard,,*.proto)
|
echo "Warning: $(1) not found. Run 'nix develop' to ensure all dependencies are available."; \
|
||||||
|
exit 1; \
|
||||||
|
}
|
||||||
|
endef
|
||||||
|
|
||||||
|
# Source file collections using shell find for better performance
|
||||||
|
GO_SOURCES := $(shell find . -name '*.go' -not -path './gen/*' -not -path './vendor/*')
|
||||||
|
PROTO_SOURCES := $(shell find . -name '*.proto' -not -path './gen/*' -not -path './vendor/*')
|
||||||
|
DOC_SOURCES := $(shell find . \( -name '*.md' -o -name '*.yaml' -o -name '*.yml' -o -name '*.ts' -o -name '*.js' -o -name '*.html' -o -name '*.css' -o -name '*.scss' -o -name '*.sass' \) -not -path './gen/*' -not -path './vendor/*' -not -path './node_modules/*')
|
||||||
|
|
||||||
|
# Default target
|
||||||
|
.PHONY: all
|
||||||
|
all: lint test build
|
||||||
|
|
||||||
|
# Dependency checking
|
||||||
|
.PHONY: check-deps
|
||||||
|
check-deps:
|
||||||
|
$(call check_tool,go)
|
||||||
|
$(call check_tool,golangci-lint)
|
||||||
|
$(call check_tool,gofumpt)
|
||||||
|
$(call check_tool,prettier)
|
||||||
|
$(call check_tool,clang-format)
|
||||||
|
$(call check_tool,buf)
|
||||||
|
|
||||||
|
# Build targets
|
||||||
|
.PHONY: build
|
||||||
|
build: check-deps $(GO_SOURCES) go.mod go.sum
|
||||||
|
@echo "Building headscale..."
|
||||||
|
go build $(PIE_FLAGS) -ldflags "-X main.version=$(VERSION)" -o headscale ./cmd/headscale
|
||||||
|
|
||||||
|
# Test targets
|
||||||
|
.PHONY: test
|
||||||
|
test: check-deps $(GO_SOURCES) go.mod go.sum
|
||||||
|
@echo "Running Go tests..."
|
||||||
|
go test -race ./...
|
||||||
|
|
||||||
|
|
||||||
build:
|
# Formatting targets
|
||||||
nix build
|
.PHONY: fmt
|
||||||
|
|
||||||
dev: lint test build
|
|
||||||
|
|
||||||
test:
|
|
||||||
gotestsum -- -short -race -coverprofile=coverage.out ./...
|
|
||||||
|
|
||||||
lint:
|
|
||||||
golangci-lint run --fix --timeout 10m
|
|
||||||
|
|
||||||
fmt: fmt-go fmt-prettier fmt-proto
|
fmt: fmt-go fmt-prettier fmt-proto
|
||||||
|
|
||||||
fmt-prettier:
|
.PHONY: fmt-go
|
||||||
prettier --write '**/**.{ts,js,md,yaml,yml,sass,css,scss,html}'
|
fmt-go: check-deps $(GO_SOURCES)
|
||||||
prettier --write --print-width 80 --prose-wrap always CHANGELOG.md
|
@echo "Formatting Go code..."
|
||||||
|
|
||||||
fmt-go:
|
|
||||||
# TODO(kradalby): Reeval if we want to use 88 in the future.
|
|
||||||
# golines --max-len=88 --base-formatter=gofumpt -w $(GO_SOURCES)
|
|
||||||
gofumpt -l -w .
|
gofumpt -l -w .
|
||||||
golangci-lint run --fix
|
golangci-lint run --fix
|
||||||
|
|
||||||
fmt-proto:
|
.PHONY: fmt-prettier
|
||||||
|
fmt-prettier: check-deps $(DOC_SOURCES)
|
||||||
|
@echo "Formatting documentation and config files..."
|
||||||
|
prettier --write '**/*.{ts,js,md,yaml,yml,sass,css,scss,html}'
|
||||||
|
prettier --write --print-width 80 --prose-wrap always CHANGELOG.md
|
||||||
|
|
||||||
|
.PHONY: fmt-proto
|
||||||
|
fmt-proto: check-deps $(PROTO_SOURCES)
|
||||||
|
@echo "Formatting Protocol Buffer files..."
|
||||||
clang-format -i $(PROTO_SOURCES)
|
clang-format -i $(PROTO_SOURCES)
|
||||||
|
|
||||||
proto-lint:
|
# Linting targets
|
||||||
cd proto/ && go run github.com/bufbuild/buf/cmd/buf lint
|
.PHONY: lint
|
||||||
|
lint: lint-go lint-proto
|
||||||
|
|
||||||
compress: build
|
.PHONY: lint-go
|
||||||
upx --brute headscale
|
lint-go: check-deps $(GO_SOURCES) go.mod go.sum
|
||||||
|
@echo "Linting Go code..."
|
||||||
|
golangci-lint run --timeout 10m
|
||||||
|
|
||||||
generate:
|
.PHONY: lint-proto
|
||||||
|
lint-proto: check-deps $(PROTO_SOURCES)
|
||||||
|
@echo "Linting Protocol Buffer files..."
|
||||||
|
cd proto/ && buf lint
|
||||||
|
|
||||||
|
# Code generation
|
||||||
|
.PHONY: generate
|
||||||
|
generate: check-deps $(PROTO_SOURCES)
|
||||||
|
@echo "Generating code from Protocol Buffers..."
|
||||||
rm -rf gen
|
rm -rf gen
|
||||||
buf generate proto
|
buf generate proto
|
||||||
|
|
||||||
|
# Clean targets
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -rf headscale gen
|
||||||
|
|
||||||
|
# Development workflow
|
||||||
|
.PHONY: dev
|
||||||
|
dev: fmt lint test build
|
||||||
|
|
||||||
|
# Help target
|
||||||
|
.PHONY: help
|
||||||
|
help:
|
||||||
|
@echo "Headscale Development Makefile"
|
||||||
|
@echo ""
|
||||||
|
@echo "Main targets:"
|
||||||
|
@echo " all - Run lint, test, and build (default)"
|
||||||
|
@echo " build - Build headscale binary"
|
||||||
|
@echo " test - Run Go tests"
|
||||||
|
@echo " fmt - Format all code (Go, docs, proto)"
|
||||||
|
@echo " lint - Lint all code (Go, proto)"
|
||||||
|
@echo " generate - Generate code from Protocol Buffers"
|
||||||
|
@echo " dev - Full development workflow (fmt + lint + test + build)"
|
||||||
|
@echo " clean - Clean build artifacts"
|
||||||
|
@echo ""
|
||||||
|
@echo "Specific targets:"
|
||||||
|
@echo " fmt-go - Format Go code only"
|
||||||
|
@echo " fmt-prettier - Format documentation only"
|
||||||
|
@echo " fmt-proto - Format Protocol Buffer files only"
|
||||||
|
@echo " lint-go - Lint Go code only"
|
||||||
|
@echo " lint-proto - Lint Protocol Buffer files only"
|
||||||
|
@echo ""
|
||||||
|
@echo "Dependencies:"
|
||||||
|
@echo " check-deps - Verify required tools are available"
|
||||||
|
@echo ""
|
||||||
|
@echo "Note: If not running in a nix shell, ensure dependencies are available:"
|
||||||
|
@echo " nix develop"
|
25
README.md
25
README.md
@ -138,16 +138,29 @@ make test
|
|||||||
|
|
||||||
To build the program:
|
To build the program:
|
||||||
|
|
||||||
```shell
|
|
||||||
nix build
|
|
||||||
```
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
make build
|
make build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Development workflow
|
||||||
|
|
||||||
|
We recommend using Nix for dependency management to ensure you have all required tools. If you prefer to manage dependencies yourself, you can use Make directly:
|
||||||
|
|
||||||
|
**With Nix (recommended):**
|
||||||
|
```shell
|
||||||
|
nix develop
|
||||||
|
make test
|
||||||
|
make build
|
||||||
|
```
|
||||||
|
|
||||||
|
**With your own dependencies:**
|
||||||
|
```shell
|
||||||
|
make test
|
||||||
|
make build
|
||||||
|
```
|
||||||
|
|
||||||
|
The Makefile will warn you if any required tools are missing and suggest running `nix develop`. Run `make help` to see all available targets.
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
<a href="https://github.com/juanfont/headscale/graphs/contributors">
|
<a href="https://github.com/juanfont/headscale/graphs/contributors">
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/docker/docker/api/types/image"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/docker/docker/errdefs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// cleanupBeforeTest performs cleanup operations before running tests.
|
// cleanupBeforeTest performs cleanup operations before running tests.
|
||||||
@ -32,7 +33,7 @@ func cleanupAfterTest(ctx context.Context, cli *client.Client, containerID strin
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// killTestContainers terminates all running test containers.
|
// killTestContainers terminates and removes all test containers.
|
||||||
func killTestContainers(ctx context.Context) error {
|
func killTestContainers(ctx context.Context) error {
|
||||||
cli, err := createDockerClient()
|
cli, err := createDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -47,28 +48,67 @@ func killTestContainers(ctx context.Context) error {
|
|||||||
return fmt.Errorf("failed to list containers: %w", err)
|
return fmt.Errorf("failed to list containers: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
killed := 0
|
removed := 0
|
||||||
for _, cont := range containers {
|
for _, cont := range containers {
|
||||||
shouldKill := false
|
shouldRemove := false
|
||||||
for _, name := range cont.Names {
|
for _, name := range cont.Names {
|
||||||
if strings.Contains(name, "headscale-test-suite") ||
|
if strings.Contains(name, "headscale-test-suite") ||
|
||||||
strings.Contains(name, "hs-") ||
|
strings.Contains(name, "hs-") ||
|
||||||
strings.Contains(name, "ts-") {
|
strings.Contains(name, "ts-") ||
|
||||||
shouldKill = true
|
strings.Contains(name, "derp-") {
|
||||||
|
shouldRemove = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if shouldKill {
|
if shouldRemove {
|
||||||
if err := cli.ContainerKill(ctx, cont.ID, "KILL"); err == nil {
|
// First kill the container if it's running
|
||||||
killed++
|
if cont.State == "running" {
|
||||||
|
_ = cli.ContainerKill(ctx, cont.ID, "KILL")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then remove the container with retry logic
|
||||||
|
if removeContainerWithRetry(ctx, cli, cont.ID) {
|
||||||
|
removed++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if removed > 0 {
|
||||||
|
fmt.Printf("Removed %d test containers\n", removed)
|
||||||
|
} else {
|
||||||
|
fmt.Println("No test containers found to remove")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// removeContainerWithRetry attempts to remove a container with exponential backoff retry logic.
|
||||||
|
func removeContainerWithRetry(ctx context.Context, cli *client.Client, containerID string) bool {
|
||||||
|
maxRetries := 3
|
||||||
|
baseDelay := 100 * time.Millisecond
|
||||||
|
|
||||||
|
for attempt := 0; attempt < maxRetries; attempt++ {
|
||||||
|
err := cli.ContainerRemove(ctx, containerID, container.RemoveOptions{
|
||||||
|
Force: true,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is the last attempt, don't wait
|
||||||
|
if attempt == maxRetries-1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait with exponential backoff
|
||||||
|
delay := baseDelay * time.Duration(1<<attempt)
|
||||||
|
time.Sleep(delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// pruneDockerNetworks removes unused Docker networks.
|
// pruneDockerNetworks removes unused Docker networks.
|
||||||
func pruneDockerNetworks(ctx context.Context) error {
|
func pruneDockerNetworks(ctx context.Context) error {
|
||||||
cli, err := createDockerClient()
|
cli, err := createDockerClient()
|
||||||
@ -77,11 +117,17 @@ func pruneDockerNetworks(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
defer cli.Close()
|
defer cli.Close()
|
||||||
|
|
||||||
_, err = cli.NetworksPrune(ctx, filters.Args{})
|
report, err := cli.NetworksPrune(ctx, filters.Args{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to prune networks: %w", err)
|
return fmt.Errorf("failed to prune networks: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(report.NetworksDeleted) > 0 {
|
||||||
|
fmt.Printf("Removed %d unused networks\n", len(report.NetworksDeleted))
|
||||||
|
} else {
|
||||||
|
fmt.Println("No unused networks found to remove")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,6 +172,12 @@ func cleanOldImages(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if removed > 0 {
|
||||||
|
fmt.Printf("Removed %d test images\n", removed)
|
||||||
|
} else {
|
||||||
|
fmt.Println("No test images found to remove")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +190,18 @@ func cleanCacheVolume(ctx context.Context) error {
|
|||||||
defer cli.Close()
|
defer cli.Close()
|
||||||
|
|
||||||
volumeName := "hs-integration-go-cache"
|
volumeName := "hs-integration-go-cache"
|
||||||
_ = cli.VolumeRemove(ctx, volumeName, true)
|
err = cli.VolumeRemove(ctx, volumeName, true)
|
||||||
|
if err != nil {
|
||||||
|
if errdefs.IsNotFound(err) {
|
||||||
|
fmt.Printf("Go module cache volume not found: %s\n", volumeName)
|
||||||
|
} else if errdefs.IsConflict(err) {
|
||||||
|
fmt.Printf("Go module cache volume is in use and cannot be removed: %s\n", volumeName)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Failed to remove Go module cache volume %s: %v\n", volumeName, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Removed Go module cache volume: %s\n", volumeName)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
376
cmd/hi/docker.go
376
cmd/hi/docker.go
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
@ -17,6 +18,8 @@ import (
|
|||||||
"github.com/docker/docker/api/types/image"
|
"github.com/docker/docker/api/types/image"
|
||||||
"github.com/docker/docker/api/types/mount"
|
"github.com/docker/docker/api/types/mount"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
|
"github.com/juanfont/headscale/integration/dockertestutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -33,7 +36,7 @@ func runTestContainer(ctx context.Context, config *RunConfig) error {
|
|||||||
}
|
}
|
||||||
defer cli.Close()
|
defer cli.Close()
|
||||||
|
|
||||||
runID := generateRunID()
|
runID := dockertestutil.GenerateRunID()
|
||||||
containerName := "headscale-test-suite-" + runID
|
containerName := "headscale-test-suite-" + runID
|
||||||
logsDir := filepath.Join(config.LogsDir, runID)
|
logsDir := filepath.Join(config.LogsDir, runID)
|
||||||
|
|
||||||
@ -89,6 +92,19 @@ func runTestContainer(ctx context.Context, config *RunConfig) error {
|
|||||||
|
|
||||||
exitCode, err := streamAndWait(ctx, cli, resp.ID)
|
exitCode, err := streamAndWait(ctx, cli, resp.ID)
|
||||||
|
|
||||||
|
// Ensure all containers have finished and logs are flushed before extracting artifacts
|
||||||
|
if waitErr := waitForContainerFinalization(ctx, cli, resp.ID, config.Verbose); waitErr != nil && config.Verbose {
|
||||||
|
log.Printf("Warning: failed to wait for container finalization: %v", waitErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract artifacts from test containers before cleanup
|
||||||
|
if err := extractArtifactsFromContainers(ctx, resp.ID, logsDir, config.Verbose); err != nil && config.Verbose {
|
||||||
|
log.Printf("Warning: failed to extract artifacts from containers: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always list control files regardless of test outcome
|
||||||
|
listControlFiles(logsDir)
|
||||||
|
|
||||||
shouldCleanup := config.CleanAfter && (!config.KeepOnFailure || exitCode == 0)
|
shouldCleanup := config.CleanAfter && (!config.KeepOnFailure || exitCode == 0)
|
||||||
if shouldCleanup {
|
if shouldCleanup {
|
||||||
if config.Verbose {
|
if config.Verbose {
|
||||||
@ -108,7 +124,6 @@ func runTestContainer(ctx context.Context, config *RunConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Test completed successfully!")
|
log.Printf("Test completed successfully!")
|
||||||
listControlFiles(logsDir)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -140,23 +155,36 @@ func createGoTestContainer(ctx context.Context, cli *client.Client, config *RunC
|
|||||||
|
|
||||||
projectRoot := findProjectRoot(pwd)
|
projectRoot := findProjectRoot(pwd)
|
||||||
|
|
||||||
|
runID := dockertestutil.ExtractRunIDFromContainerName(containerName)
|
||||||
|
|
||||||
env := []string{
|
env := []string{
|
||||||
fmt.Sprintf("HEADSCALE_INTEGRATION_POSTGRES=%d", boolToInt(config.UsePostgres)),
|
fmt.Sprintf("HEADSCALE_INTEGRATION_POSTGRES=%d", boolToInt(config.UsePostgres)),
|
||||||
|
fmt.Sprintf("HEADSCALE_INTEGRATION_RUN_ID=%s", runID),
|
||||||
}
|
}
|
||||||
|
|
||||||
containerConfig := &container.Config{
|
containerConfig := &container.Config{
|
||||||
Image: "golang:" + config.GoVersion,
|
Image: "golang:" + config.GoVersion,
|
||||||
Cmd: goTestCmd,
|
Cmd: goTestCmd,
|
||||||
Env: env,
|
Env: env,
|
||||||
WorkingDir: projectRoot + "/integration",
|
WorkingDir: projectRoot + "/integration",
|
||||||
Tty: true,
|
Tty: true,
|
||||||
|
Labels: map[string]string{
|
||||||
|
"hi.run-id": runID,
|
||||||
|
"hi.test-type": "test-runner",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the correct Docker socket path from the current context
|
||||||
|
dockerSocketPath := getDockerSocketPath()
|
||||||
|
|
||||||
|
if config.Verbose {
|
||||||
|
log.Printf("Using Docker socket: %s", dockerSocketPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
hostConfig := &container.HostConfig{
|
hostConfig := &container.HostConfig{
|
||||||
AutoRemove: false, // We'll remove manually for better control
|
AutoRemove: false, // We'll remove manually for better control
|
||||||
Binds: []string{
|
Binds: []string{
|
||||||
fmt.Sprintf("%s:%s", projectRoot, projectRoot),
|
fmt.Sprintf("%s:%s", projectRoot, projectRoot),
|
||||||
"/var/run/docker.sock:/var/run/docker.sock",
|
fmt.Sprintf("%s:/var/run/docker.sock", dockerSocketPath),
|
||||||
logsDir + ":/tmp/control",
|
logsDir + ":/tmp/control",
|
||||||
},
|
},
|
||||||
Mounts: []mount.Mount{
|
Mounts: []mount.Mount{
|
||||||
@ -200,13 +228,69 @@ func streamAndWait(ctx context.Context, cli *client.Client, containerID string)
|
|||||||
return -1, ErrUnexpectedContainerWait
|
return -1, ErrUnexpectedContainerWait
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateRunID creates a unique timestamp-based run identifier.
|
// waitForContainerFinalization ensures all test containers have properly finished and flushed their output.
|
||||||
func generateRunID() string {
|
func waitForContainerFinalization(ctx context.Context, cli *client.Client, testContainerID string, verbose bool) error {
|
||||||
now := time.Now()
|
// First, get all related test containers
|
||||||
timestamp := now.Format("20060102-150405")
|
containers, err := cli.ContainerList(ctx, container.ListOptions{All: true})
|
||||||
return timestamp
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to list containers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testContainers := getCurrentTestContainers(containers, testContainerID, verbose)
|
||||||
|
|
||||||
|
// Wait for all test containers to reach a final state
|
||||||
|
maxWaitTime := 10 * time.Second
|
||||||
|
checkInterval := 500 * time.Millisecond
|
||||||
|
timeout := time.After(maxWaitTime)
|
||||||
|
ticker := time.NewTicker(checkInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-timeout:
|
||||||
|
if verbose {
|
||||||
|
log.Printf("Timeout waiting for container finalization, proceeding with artifact extraction")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case <-ticker.C:
|
||||||
|
allFinalized := true
|
||||||
|
|
||||||
|
for _, testCont := range testContainers {
|
||||||
|
inspect, err := cli.ContainerInspect(ctx, testCont.ID)
|
||||||
|
if err != nil {
|
||||||
|
if verbose {
|
||||||
|
log.Printf("Warning: failed to inspect container %s: %v", testCont.name, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if container is in a final state
|
||||||
|
if !isContainerFinalized(inspect.State) {
|
||||||
|
allFinalized = false
|
||||||
|
if verbose {
|
||||||
|
log.Printf("Container %s still finalizing (state: %s)", testCont.name, inspect.State.Status)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if allFinalized {
|
||||||
|
if verbose {
|
||||||
|
log.Printf("All test containers finalized, ready for artifact extraction")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isContainerFinalized checks if a container has reached a final state where logs are flushed.
|
||||||
|
func isContainerFinalized(state *container.State) bool {
|
||||||
|
// Container is finalized if it's not running and has a finish time
|
||||||
|
return !state.Running && state.FinishedAt != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// findProjectRoot locates the project root by finding the directory containing go.mod.
|
// findProjectRoot locates the project root by finding the directory containing go.mod.
|
||||||
func findProjectRoot(startPath string) string {
|
func findProjectRoot(startPath string) string {
|
||||||
current := startPath
|
current := startPath
|
||||||
@ -288,6 +372,13 @@ func getCurrentDockerContext() (*DockerContext, error) {
|
|||||||
return nil, ErrNoDockerContext
|
return nil, ErrNoDockerContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getDockerSocketPath returns the correct Docker socket path for the current context.
|
||||||
|
func getDockerSocketPath() string {
|
||||||
|
// Always use the default socket path for mounting since Docker handles
|
||||||
|
// the translation to the actual socket (e.g., colima socket) internally
|
||||||
|
return "/var/run/docker.sock"
|
||||||
|
}
|
||||||
|
|
||||||
// ensureImageAvailable pulls the specified Docker image to ensure it's available.
|
// ensureImageAvailable pulls the specified Docker image to ensure it's available.
|
||||||
func ensureImageAvailable(ctx context.Context, cli *client.Client, imageName string, verbose bool) error {
|
func ensureImageAvailable(ctx context.Context, cli *client.Client, imageName string, verbose bool) error {
|
||||||
if verbose {
|
if verbose {
|
||||||
@ -325,24 +416,29 @@ func listControlFiles(logsDir string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var logFiles []string
|
var logFiles []string
|
||||||
var tarFiles []string
|
var dataFiles []string
|
||||||
|
var dataDirs []string
|
||||||
|
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
if entry.IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
name := entry.Name()
|
name := entry.Name()
|
||||||
// Only show headscale (hs-*) files
|
// Only show headscale (hs-*) files and directories
|
||||||
if !strings.HasPrefix(name, "hs-") {
|
if !strings.HasPrefix(name, "hs-") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
if entry.IsDir() {
|
||||||
case strings.HasSuffix(name, ".stderr.log") || strings.HasSuffix(name, ".stdout.log"):
|
// Include directories (pprof, mapresponses)
|
||||||
logFiles = append(logFiles, name)
|
if strings.Contains(name, "-pprof") || strings.Contains(name, "-mapresponses") {
|
||||||
case strings.HasSuffix(name, ".pprof.tar") || strings.HasSuffix(name, ".maps.tar") || strings.HasSuffix(name, ".db.tar"):
|
dataDirs = append(dataDirs, name)
|
||||||
tarFiles = append(tarFiles, name)
|
}
|
||||||
|
} else {
|
||||||
|
// Include files
|
||||||
|
switch {
|
||||||
|
case strings.HasSuffix(name, ".stderr.log") || strings.HasSuffix(name, ".stdout.log"):
|
||||||
|
logFiles = append(logFiles, name)
|
||||||
|
case strings.HasSuffix(name, ".db"):
|
||||||
|
dataFiles = append(dataFiles, name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,10 +451,244 @@ func listControlFiles(logsDir string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(tarFiles) > 0 {
|
if len(dataFiles) > 0 || len(dataDirs) > 0 {
|
||||||
log.Printf("Headscale archives:")
|
log.Printf("Headscale data:")
|
||||||
for _, file := range tarFiles {
|
for _, file := range dataFiles {
|
||||||
log.Printf(" %s", file)
|
log.Printf(" %s", file)
|
||||||
}
|
}
|
||||||
|
for _, dir := range dataDirs {
|
||||||
|
log.Printf(" %s/", dir)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extractArtifactsFromContainers collects container logs and files from the specific test run.
|
||||||
|
func extractArtifactsFromContainers(ctx context.Context, testContainerID, logsDir string, verbose bool) error {
|
||||||
|
cli, err := createDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create Docker client: %w", err)
|
||||||
|
}
|
||||||
|
defer cli.Close()
|
||||||
|
|
||||||
|
// List all containers
|
||||||
|
containers, err := cli.ContainerList(ctx, container.ListOptions{All: true})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to list containers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get containers from the specific test run
|
||||||
|
currentTestContainers := getCurrentTestContainers(containers, testContainerID, verbose)
|
||||||
|
|
||||||
|
extractedCount := 0
|
||||||
|
for _, cont := range currentTestContainers {
|
||||||
|
// Extract container logs and tar files
|
||||||
|
if err := extractContainerArtifacts(ctx, cli, cont.ID, cont.name, logsDir, verbose); err != nil {
|
||||||
|
if verbose {
|
||||||
|
log.Printf("Warning: failed to extract artifacts from container %s (%s): %v", cont.name, cont.ID[:12], err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if verbose {
|
||||||
|
log.Printf("Extracted artifacts from container %s (%s)", cont.name, cont.ID[:12])
|
||||||
|
}
|
||||||
|
extractedCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose && extractedCount > 0 {
|
||||||
|
log.Printf("Extracted artifacts from %d containers", extractedCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// testContainer represents a container from the current test run.
|
||||||
|
type testContainer struct {
|
||||||
|
ID string
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCurrentTestContainers filters containers to only include those from the current test run.
|
||||||
|
func getCurrentTestContainers(containers []container.Summary, testContainerID string, verbose bool) []testContainer {
|
||||||
|
var testRunContainers []testContainer
|
||||||
|
|
||||||
|
// Find the test container to get its run ID label
|
||||||
|
var runID string
|
||||||
|
for _, cont := range containers {
|
||||||
|
if cont.ID == testContainerID {
|
||||||
|
if cont.Labels != nil {
|
||||||
|
runID = cont.Labels["hi.run-id"]
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if runID == "" {
|
||||||
|
log.Printf("Error: test container %s missing required hi.run-id label", testContainerID[:12])
|
||||||
|
return testRunContainers
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
log.Printf("Looking for containers with run ID: %s", runID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all containers with the same run ID
|
||||||
|
for _, cont := range containers {
|
||||||
|
for _, name := range cont.Names {
|
||||||
|
containerName := strings.TrimPrefix(name, "/")
|
||||||
|
if strings.HasPrefix(containerName, "hs-") || strings.HasPrefix(containerName, "ts-") {
|
||||||
|
// Check if container has matching run ID label
|
||||||
|
if cont.Labels != nil && cont.Labels["hi.run-id"] == runID {
|
||||||
|
testRunContainers = append(testRunContainers, testContainer{
|
||||||
|
ID: cont.ID,
|
||||||
|
name: containerName,
|
||||||
|
})
|
||||||
|
if verbose {
|
||||||
|
log.Printf("Including container %s (run ID: %s)", containerName, runID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return testRunContainers
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractContainerArtifacts saves logs and tar files from a container.
|
||||||
|
func extractContainerArtifacts(ctx context.Context, cli *client.Client, containerID, containerName, logsDir string, verbose bool) error {
|
||||||
|
// Ensure the logs directory exists
|
||||||
|
if err := os.MkdirAll(logsDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create logs directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract container logs
|
||||||
|
if err := extractContainerLogs(ctx, cli, containerID, containerName, logsDir, verbose); err != nil {
|
||||||
|
return fmt.Errorf("failed to extract logs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract tar files for headscale containers only
|
||||||
|
if strings.HasPrefix(containerName, "hs-") {
|
||||||
|
if err := extractContainerFiles(ctx, cli, containerID, containerName, logsDir, verbose); err != nil {
|
||||||
|
if verbose {
|
||||||
|
log.Printf("Warning: failed to extract files from %s: %v", containerName, err)
|
||||||
|
}
|
||||||
|
// Don't fail the whole extraction if files are missing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractContainerLogs saves the stdout and stderr logs from a container to files.
|
||||||
|
func extractContainerLogs(ctx context.Context, cli *client.Client, containerID, containerName, logsDir string, verbose bool) error {
|
||||||
|
// Get container logs
|
||||||
|
logReader, err := cli.ContainerLogs(ctx, containerID, container.LogsOptions{
|
||||||
|
ShowStdout: true,
|
||||||
|
ShowStderr: true,
|
||||||
|
Timestamps: false,
|
||||||
|
Follow: false,
|
||||||
|
Tail: "all",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get container logs: %w", err)
|
||||||
|
}
|
||||||
|
defer logReader.Close()
|
||||||
|
|
||||||
|
// Create log files following the headscale naming convention
|
||||||
|
stdoutPath := filepath.Join(logsDir, containerName+".stdout.log")
|
||||||
|
stderrPath := filepath.Join(logsDir, containerName+".stderr.log")
|
||||||
|
|
||||||
|
// Create buffers to capture stdout and stderr separately
|
||||||
|
var stdoutBuf, stderrBuf bytes.Buffer
|
||||||
|
|
||||||
|
// Demultiplex the Docker logs stream to separate stdout and stderr
|
||||||
|
_, err = stdcopy.StdCopy(&stdoutBuf, &stderrBuf, logReader)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to demultiplex container logs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write stdout logs
|
||||||
|
if err := os.WriteFile(stdoutPath, stdoutBuf.Bytes(), 0644); err != nil {
|
||||||
|
return fmt.Errorf("failed to write stdout log: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write stderr logs
|
||||||
|
if err := os.WriteFile(stderrPath, stderrBuf.Bytes(), 0644); err != nil {
|
||||||
|
return fmt.Errorf("failed to write stderr log: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
log.Printf("Saved logs for %s: %s, %s", containerName, stdoutPath, stderrPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractContainerFiles extracts database file and directories from headscale containers.
|
||||||
|
// Note: The actual file extraction is now handled by the integration tests themselves
|
||||||
|
// via SaveProfile, SaveMapResponses, and SaveDatabase functions in hsic.go
|
||||||
|
func extractContainerFiles(ctx context.Context, cli *client.Client, containerID, containerName, logsDir string, verbose bool) error {
|
||||||
|
// Files are now extracted directly by the integration tests
|
||||||
|
// This function is kept for potential future use or other file types
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// logExtractionError logs extraction errors with appropriate level based on error type.
|
||||||
|
func logExtractionError(artifactType, containerName string, err error, verbose bool) {
|
||||||
|
if errors.Is(err, ErrFileNotFoundInTar) {
|
||||||
|
// File not found is expected and only logged in verbose mode
|
||||||
|
if verbose {
|
||||||
|
log.Printf("No %s found in container %s", artifactType, containerName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Other errors are actual failures and should be logged as warnings
|
||||||
|
log.Printf("Warning: failed to extract %s from %s: %v", artifactType, containerName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractSingleFile copies a single file from a container.
|
||||||
|
func extractSingleFile(ctx context.Context, cli *client.Client, containerID, sourcePath, fileName, logsDir string, verbose bool) error {
|
||||||
|
tarReader, _, err := cli.CopyFromContainer(ctx, containerID, sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to copy %s from container: %w", sourcePath, err)
|
||||||
|
}
|
||||||
|
defer tarReader.Close()
|
||||||
|
|
||||||
|
// Extract the single file from the tar
|
||||||
|
filePath := filepath.Join(logsDir, fileName)
|
||||||
|
if err := extractFileFromTar(tarReader, filepath.Base(sourcePath), filePath); err != nil {
|
||||||
|
return fmt.Errorf("failed to extract file from tar: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
log.Printf("Extracted %s from %s", fileName, containerID[:12])
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractDirectory copies a directory from a container and extracts its contents.
|
||||||
|
func extractDirectory(ctx context.Context, cli *client.Client, containerID, sourcePath, dirName, logsDir string, verbose bool) error {
|
||||||
|
tarReader, _, err := cli.CopyFromContainer(ctx, containerID, sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to copy %s from container: %w", sourcePath, err)
|
||||||
|
}
|
||||||
|
defer tarReader.Close()
|
||||||
|
|
||||||
|
// Create target directory
|
||||||
|
targetDir := filepath.Join(logsDir, dirName)
|
||||||
|
if err := os.MkdirAll(targetDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create directory %s: %w", targetDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the directory from the tar
|
||||||
|
if err := extractDirectoryFromTar(tarReader, targetDir); err != nil {
|
||||||
|
return fmt.Errorf("failed to extract directory from tar: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
log.Printf("Extracted %s/ from %s", dirName, containerID[:12])
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -7,8 +7,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/client"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrSystemChecksFailed = errors.New("system checks failed")
|
var ErrSystemChecksFailed = errors.New("system checks failed")
|
||||||
@ -88,7 +86,7 @@ func checkDockerBinary() DoctorResult {
|
|||||||
|
|
||||||
// checkDockerDaemon verifies Docker daemon is running and accessible.
|
// checkDockerDaemon verifies Docker daemon is running and accessible.
|
||||||
func checkDockerDaemon(ctx context.Context) DoctorResult {
|
func checkDockerDaemon(ctx context.Context) DoctorResult {
|
||||||
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
cli, err := createDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DoctorResult{
|
return DoctorResult{
|
||||||
Name: "Docker Daemon",
|
Name: "Docker Daemon",
|
||||||
|
101
cmd/hi/tar_utils.go
Normal file
101
cmd/hi/tar_utils.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrFileNotFoundInTar indicates a file was not found in the tar archive.
|
||||||
|
ErrFileNotFoundInTar = errors.New("file not found in tar")
|
||||||
|
)
|
||||||
|
|
||||||
|
// extractFileFromTar extracts a single file from a tar reader.
|
||||||
|
func extractFileFromTar(tarReader io.Reader, fileName, outputPath string) error {
|
||||||
|
tr := tar.NewReader(tarReader)
|
||||||
|
|
||||||
|
for {
|
||||||
|
header, err := tr.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read tar header: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is the file we're looking for
|
||||||
|
if filepath.Base(header.Name) == fileName {
|
||||||
|
if header.Typeflag == tar.TypeReg {
|
||||||
|
// Create the output file
|
||||||
|
outFile, err := os.Create(outputPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create output file: %w", err)
|
||||||
|
}
|
||||||
|
defer outFile.Close()
|
||||||
|
|
||||||
|
// Copy file contents
|
||||||
|
if _, err := io.Copy(outFile, tr); err != nil {
|
||||||
|
return fmt.Errorf("failed to copy file contents: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("%w: %s", ErrFileNotFoundInTar, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractDirectoryFromTar extracts all files from a tar reader to a target directory.
|
||||||
|
func extractDirectoryFromTar(tarReader io.Reader, targetDir string) error {
|
||||||
|
tr := tar.NewReader(tarReader)
|
||||||
|
|
||||||
|
for {
|
||||||
|
header, err := tr.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read tar header: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean the path to prevent directory traversal
|
||||||
|
cleanName := filepath.Clean(header.Name)
|
||||||
|
if strings.Contains(cleanName, "..") {
|
||||||
|
continue // Skip potentially dangerous paths
|
||||||
|
}
|
||||||
|
|
||||||
|
targetPath := filepath.Join(targetDir, filepath.Base(cleanName))
|
||||||
|
|
||||||
|
switch header.Typeflag {
|
||||||
|
case tar.TypeDir:
|
||||||
|
// Create directory
|
||||||
|
if err := os.MkdirAll(targetPath, os.FileMode(header.Mode)); err != nil {
|
||||||
|
return fmt.Errorf("failed to create directory %s: %w", targetPath, err)
|
||||||
|
}
|
||||||
|
case tar.TypeReg:
|
||||||
|
// Create file
|
||||||
|
outFile, err := os.Create(targetPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create file %s: %w", targetPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.Copy(outFile, tr); err != nil {
|
||||||
|
outFile.Close()
|
||||||
|
return fmt.Errorf("failed to copy file contents: %w", err)
|
||||||
|
}
|
||||||
|
outFile.Close()
|
||||||
|
|
||||||
|
// Set file permissions
|
||||||
|
if err := os.Chmod(targetPath, os.FileMode(header.Mode)); err != nil {
|
||||||
|
return fmt.Errorf("failed to set file permissions: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
17
flake.nix
17
flake.nix
@ -19,6 +19,7 @@
|
|||||||
overlay = _: prev: let
|
overlay = _: prev: let
|
||||||
pkgs = nixpkgs.legacyPackages.${prev.system};
|
pkgs = nixpkgs.legacyPackages.${prev.system};
|
||||||
buildGo = pkgs.buildGo124Module;
|
buildGo = pkgs.buildGo124Module;
|
||||||
|
vendorHash = "sha256-9e+ngBkzRb3anSYtFHTJDxt/VMzrHdb5NWwOesJz+kY=";
|
||||||
in {
|
in {
|
||||||
headscale = buildGo {
|
headscale = buildGo {
|
||||||
pname = "headscale";
|
pname = "headscale";
|
||||||
@ -30,7 +31,7 @@
|
|||||||
|
|
||||||
# When updating go.mod or go.sum, a new sha will need to be calculated,
|
# When updating go.mod or go.sum, a new sha will need to be calculated,
|
||||||
# update this if you have a mismatch after doing a change to those files.
|
# update this if you have a mismatch after doing a change to those files.
|
||||||
vendorHash = "sha256-8nRaQNwUDbHkp3q54R6eLDh1GkfwBlh4b9w0IkNj2sY=";
|
inherit vendorHash;
|
||||||
|
|
||||||
subPackages = ["cmd/headscale"];
|
subPackages = ["cmd/headscale"];
|
||||||
|
|
||||||
@ -42,6 +43,17 @@
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
hi = buildGo {
|
||||||
|
pname = "hi";
|
||||||
|
version = headscaleVersion;
|
||||||
|
src = pkgs.lib.cleanSource self;
|
||||||
|
|
||||||
|
checkFlags = ["-short"];
|
||||||
|
inherit vendorHash;
|
||||||
|
|
||||||
|
subPackages = ["cmd/hi"];
|
||||||
|
};
|
||||||
|
|
||||||
protoc-gen-grpc-gateway = buildGo rec {
|
protoc-gen-grpc-gateway = buildGo rec {
|
||||||
pname = "grpc-gateway";
|
pname = "grpc-gateway";
|
||||||
version = "2.24.0";
|
version = "2.24.0";
|
||||||
@ -144,6 +156,9 @@
|
|||||||
buf
|
buf
|
||||||
clang-tools # clang-format
|
clang-tools # clang-format
|
||||||
protobuf-language-server
|
protobuf-language-server
|
||||||
|
|
||||||
|
# Add hi to make it even easier to use ci runner.
|
||||||
|
hi
|
||||||
];
|
];
|
||||||
|
|
||||||
# Add entry to build a docker image with headscale
|
# Add entry to build a docker image with headscale
|
||||||
|
55
go.mod
55
go.mod
@ -7,14 +7,14 @@ toolchain go1.24.2
|
|||||||
require (
|
require (
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||||
github.com/arl/statsviz v0.6.0
|
github.com/arl/statsviz v0.6.0
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0
|
github.com/cenkalti/backoff/v5 v5.0.2
|
||||||
github.com/chasefleming/elem-go v0.30.0
|
github.com/chasefleming/elem-go v0.30.0
|
||||||
github.com/coder/websocket v1.8.13
|
github.com/coder/websocket v1.8.13
|
||||||
github.com/coreos/go-oidc/v3 v3.14.1
|
github.com/coreos/go-oidc/v3 v3.14.1
|
||||||
github.com/creachadair/command v0.1.22
|
github.com/creachadair/command v0.1.22
|
||||||
github.com/creachadair/flax v0.0.5
|
github.com/creachadair/flax v0.0.5
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
||||||
github.com/docker/docker v28.1.1+incompatible
|
github.com/docker/docker v28.2.2+incompatible
|
||||||
github.com/fsnotify/fsnotify v1.9.0
|
github.com/fsnotify/fsnotify v1.9.0
|
||||||
github.com/glebarez/sqlite v1.11.0
|
github.com/glebarez/sqlite v1.11.0
|
||||||
github.com/go-gormigrate/gormigrate/v2 v2.1.4
|
github.com/go-gormigrate/gormigrate/v2 v2.1.4
|
||||||
@ -22,7 +22,7 @@ require (
|
|||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
|
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0
|
||||||
github.com/jagottsicher/termcolor v1.0.2
|
github.com/jagottsicher/termcolor v1.0.2
|
||||||
github.com/klauspost/compress v1.18.0
|
github.com/klauspost/compress v1.18.0
|
||||||
github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25
|
github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25
|
||||||
@ -30,11 +30,11 @@ require (
|
|||||||
github.com/philip-bui/grpc-zerolog v1.0.1
|
github.com/philip-bui/grpc-zerolog v1.0.1
|
||||||
github.com/pkg/profile v1.7.0
|
github.com/pkg/profile v1.7.0
|
||||||
github.com/prometheus/client_golang v1.22.0
|
github.com/prometheus/client_golang v1.22.0
|
||||||
github.com/prometheus/common v0.63.0
|
github.com/prometheus/common v0.65.0
|
||||||
github.com/pterm/pterm v0.12.80
|
github.com/pterm/pterm v0.12.81
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1
|
github.com/puzpuzpuz/xsync/v4 v4.1.0
|
||||||
github.com/rs/zerolog v1.34.0
|
github.com/rs/zerolog v1.34.0
|
||||||
github.com/samber/lo v1.50.0
|
github.com/samber/lo v1.51.0
|
||||||
github.com/sasha-s/go-deadlock v0.3.5
|
github.com/sasha-s/go-deadlock v0.3.5
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/spf13/viper v1.20.1
|
github.com/spf13/viper v1.20.1
|
||||||
@ -43,20 +43,20 @@ require (
|
|||||||
github.com/tailscale/tailsql v0.0.0-20250421235516-02f85f087b97
|
github.com/tailscale/tailsql v0.0.0-20250421235516-02f85f087b97
|
||||||
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
|
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/crypto v0.38.0
|
golang.org/x/crypto v0.39.0
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
|
||||||
golang.org/x/net v0.40.0
|
golang.org/x/net v0.41.0
|
||||||
golang.org/x/oauth2 v0.29.0
|
golang.org/x/oauth2 v0.30.0
|
||||||
golang.org/x/sync v0.14.0
|
golang.org/x/sync v0.15.0
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237
|
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822
|
||||||
google.golang.org/grpc v1.72.1
|
google.golang.org/grpc v1.73.0
|
||||||
google.golang.org/protobuf v1.36.6
|
google.golang.org/protobuf v1.36.6
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gorm.io/driver/postgres v1.5.11
|
gorm.io/driver/postgres v1.6.0
|
||||||
gorm.io/gorm v1.25.12
|
gorm.io/gorm v1.30.0
|
||||||
tailscale.com v1.83.0-pre.0.20250331211809-96fe8a6db6c9
|
tailscale.com v1.84.2
|
||||||
zgo.at/zcache/v2 v2.1.0
|
zgo.at/zcache/v2 v2.2.0
|
||||||
zombiezen.com/go/postgrestest v1.0.1
|
zombiezen.com/go/postgrestest v1.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -110,9 +110,12 @@ require (
|
|||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.13 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sts v1.33.13 // indirect
|
||||||
github.com/aws/smithy-go v1.22.2 // indirect
|
github.com/aws/smithy-go v1.22.2 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/containerd/console v1.0.4 // indirect
|
github.com/containerd/console v1.0.5 // indirect
|
||||||
github.com/containerd/continuity v0.4.5 // indirect
|
github.com/containerd/continuity v0.4.5 // indirect
|
||||||
|
github.com/containerd/errdefs v0.3.0 // indirect
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
|
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
|
||||||
github.com/creachadair/mds v0.24.1 // indirect
|
github.com/creachadair/mds v0.24.1 // indirect
|
||||||
github.com/dblohm7/wingoes v0.0.0-20240123200102-b75a8a7d7eb0 // indirect
|
github.com/dblohm7/wingoes v0.0.0-20240123200102-b75a8a7d7eb0 // indirect
|
||||||
@ -154,7 +157,6 @@ require (
|
|||||||
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
|
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
|
||||||
github.com/illarion/gonotify/v3 v3.0.2 // indirect
|
github.com/illarion/gonotify/v3 v3.0.2 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20240129002554-15c9b8791914 // indirect
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/pgx/v5 v5.7.4 // indirect
|
github.com/jackc/pgx/v5 v5.7.4 // indirect
|
||||||
@ -164,7 +166,6 @@ require (
|
|||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
github.com/jsimonetti/rtnetlink v1.4.1 // indirect
|
github.com/jsimonetti/rtnetlink v1.4.1 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect
|
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/lib/pq v1.10.9 // indirect
|
github.com/lib/pq v1.10.9 // indirect
|
||||||
@ -191,11 +192,10 @@ require (
|
|||||||
github.com/opencontainers/runc v1.3.0 // indirect
|
github.com/opencontainers/runc v1.3.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a // indirect
|
github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/prometheus-community/pro-bing v0.4.0 // indirect
|
github.com/prometheus-community/pro-bing v0.4.0 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
@ -216,8 +216,7 @@ require (
|
|||||||
github.com/tailscale/setec v0.0.0-20250305161714-445cadbbca3d // indirect
|
github.com/tailscale/setec v0.0.0-20250305161714-445cadbbca3d // indirect
|
||||||
github.com/tailscale/squibble v0.0.0-20250108170732-a4ca58afa694 // indirect
|
github.com/tailscale/squibble v0.0.0-20250108170732-a4ca58afa694 // indirect
|
||||||
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
|
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
|
||||||
github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19 // indirect
|
github.com/tailscale/wireguard-go v0.0.0-20250304000100-91a0587fb251 // indirect
|
||||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
|
||||||
github.com/vishvananda/netns v0.0.4 // indirect
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||||
@ -233,14 +232,14 @@ require (
|
|||||||
go.opentelemetry.io/otel/trace v1.36.0 // indirect
|
go.opentelemetry.io/otel/trace v1.36.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
|
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
|
||||||
golang.org/x/mod v0.24.0 // indirect
|
golang.org/x/mod v0.25.0 // indirect
|
||||||
golang.org/x/sys v0.33.0 // indirect
|
golang.org/x/sys v0.33.0 // indirect
|
||||||
golang.org/x/term v0.32.0 // indirect
|
golang.org/x/term v0.32.0 // indirect
|
||||||
golang.org/x/text v0.25.0 // indirect
|
golang.org/x/text v0.26.0 // indirect
|
||||||
golang.org/x/time v0.10.0 // indirect
|
golang.org/x/time v0.10.0 // indirect
|
||||||
golang.org/x/tools v0.32.0 // indirect
|
golang.org/x/tools v0.33.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 // indirect
|
gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 // indirect
|
||||||
)
|
)
|
||||||
|
117
go.sum
117
go.sum
@ -1,3 +1,5 @@
|
|||||||
|
9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f h1:1C7nZuxUMNz7eiQALRfiqNOm04+m3edWlRff/BYHf0Q=
|
||||||
|
9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f/go.mod h1:hHyrZRryGqVdqrknjq5OWDLGCTJ2NeEvtrpR96mjraM=
|
||||||
atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg=
|
atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg=
|
||||||
atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ=
|
atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ=
|
||||||
atomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw=
|
atomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw=
|
||||||
@ -85,7 +87,6 @@ github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxY
|
|||||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
|
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
|
||||||
@ -111,10 +112,14 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
|
|||||||
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
|
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
|
||||||
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||||
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
|
github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc=
|
||||||
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||||
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
|
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
|
||||||
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
|
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
|
||||||
|
github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4=
|
||||||
|
github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
|
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
|
||||||
@ -129,6 +134,8 @@ github.com/creachadair/flax v0.0.5 h1:zt+CRuXQASxwQ68e9GHAOnEgAU29nF0zYMHOCrL5wz
|
|||||||
github.com/creachadair/flax v0.0.5/go.mod h1:F1PML0JZLXSNDMNiRGK2yjm5f+L9QCHchyHBldFymj8=
|
github.com/creachadair/flax v0.0.5/go.mod h1:F1PML0JZLXSNDMNiRGK2yjm5f+L9QCHchyHBldFymj8=
|
||||||
github.com/creachadair/mds v0.24.1 h1:bzL4ItCtAUxxO9KkotP0PVzlw4tnJicAcjPu82v2mGs=
|
github.com/creachadair/mds v0.24.1 h1:bzL4ItCtAUxxO9KkotP0PVzlw4tnJicAcjPu82v2mGs=
|
||||||
github.com/creachadair/mds v0.24.1/go.mod h1:ArfS0vPHoLV/SzuIzoqTEZfoYmac7n9Cj8XPANHocvw=
|
github.com/creachadair/mds v0.24.1/go.mod h1:ArfS0vPHoLV/SzuIzoqTEZfoYmac7n9Cj8XPANHocvw=
|
||||||
|
github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoidf0MdVc=
|
||||||
|
github.com/creachadair/taskgroup v0.13.2/go.mod h1:i3V1Zx7H8RjwljUEeUWYT30Lmb9poewSb2XI1yTwD0g=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0=
|
github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0=
|
||||||
@ -147,8 +154,8 @@ github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
|||||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||||
github.com/docker/cli v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5MdSpiItp8k=
|
github.com/docker/cli v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5MdSpiItp8k=
|
||||||
github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
|
github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
|
||||||
github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
@ -202,6 +209,8 @@ github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqw
|
|||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
|
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737 h1:cf60tHxREO3g1nroKr2osU3JWZsJzkfi7rEg+oAB0Lo=
|
||||||
|
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737/go.mod h1:MIS0jDzbU/vuM9MC4YnBITCv+RYuTRq8dJzmCrFsK9g=
|
||||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||||
@ -234,6 +243,8 @@ github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4r
|
|||||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
|
github.com/google/go-tpm v0.9.4 h1:awZRf9FwOeTunQmHoDYSHJps3ie6f1UlhS1fOdPEt1I=
|
||||||
|
github.com/google/go-tpm v0.9.4/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI=
|
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI=
|
||||||
@ -260,8 +271,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
|
|||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
|
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
|
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0 h1:+epNPbD5EqgpEMm5wrl4Hqts3jZt8+kYaqUisuuIGTk=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
|
||||||
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
||||||
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
|
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
|
||||||
@ -414,10 +425,10 @@ github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykU
|
|||||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||||
github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
|
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||||
github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
|
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
|
github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
|
||||||
@ -427,10 +438,10 @@ github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEej
|
|||||||
github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
|
github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
|
||||||
github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
|
github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
|
||||||
github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s=
|
github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s=
|
||||||
github.com/pterm/pterm v0.12.80 h1:mM55B+GnKUnLMUSqhdINe4s6tOuVQIetQ3my8JGyAIg=
|
github.com/pterm/pterm v0.12.81 h1:ju+j5I2++FO1jBKMmscgh5h5DPFDFMB7epEjSoKehKA=
|
||||||
github.com/pterm/pterm v0.12.80/go.mod h1:c6DeF9bSnOSeFPZlfs4ZRAFcf5SCoTwvwQ5xaKGQlHo=
|
github.com/pterm/pterm v0.12.81/go.mod h1:TyuyrPjnxfwP+ccJdBTeWHtd/e0ybQHkOS/TakajZCw=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
github.com/puzpuzpuz/xsync/v4 v4.1.0 h1:x9eHRl4QhZFIPJ17yl4KKW9xLyVWbb3/Yq4SXpjF71U=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
github.com/puzpuzpuz/xsync/v4 v4.1.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
@ -447,8 +458,8 @@ github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP
|
|||||||
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
|
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
|
||||||
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
|
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
|
||||||
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
|
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
|
||||||
github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY=
|
github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI=
|
||||||
github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc=
|
github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||||
github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU=
|
github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU=
|
||||||
github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U=
|
github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U=
|
||||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
@ -489,8 +500,8 @@ github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP
|
|||||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
|
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
|
||||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
|
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
|
||||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
|
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
|
||||||
github.com/tailscale/golang-x-crypto v0.0.0-20250218230618-9a281fd8faca h1:ecjHwH73Yvqf/oIdQ2vxAX+zc6caQsYdPzsxNW1J3G8=
|
github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869 h1:SRL6irQkKGQKKLzvQP/ke/2ZuB7Py5+XuqtOgSj+iMM=
|
||||||
github.com/tailscale/golang-x-crypto v0.0.0-20250218230618-9a281fd8faca/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ=
|
github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ=
|
||||||
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio=
|
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio=
|
||||||
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=
|
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=
|
||||||
github.com/tailscale/hujson v0.0.0-20250226034555-ec1d1c113d33 h1:idh63uw+gsG05HwjZsAENCG4KZfyvjK03bpjxa5qRRk=
|
github.com/tailscale/hujson v0.0.0-20250226034555-ec1d1c113d33 h1:idh63uw+gsG05HwjZsAENCG4KZfyvjK03bpjxa5qRRk=
|
||||||
@ -509,8 +520,8 @@ github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:U
|
|||||||
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
|
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
|
||||||
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M=
|
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M=
|
||||||
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y=
|
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y=
|
||||||
github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19 h1:BcEJP2ewTIK2ZCsqgl6YGpuO6+oKqqag5HHb7ehljKw=
|
github.com/tailscale/wireguard-go v0.0.0-20250304000100-91a0587fb251 h1:h/41LFTrwMxB9Xvvug0kRdQCU5TlV1+pAMQw0ZtDE3U=
|
||||||
github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
|
github.com/tailscale/wireguard-go v0.0.0-20250304000100-91a0587fb251/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
|
||||||
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek=
|
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek=
|
||||||
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg=
|
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg=
|
||||||
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
|
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
|
||||||
@ -519,8 +530,8 @@ github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e h1:IWllFTiDjjLIf2
|
|||||||
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e/go.mod h1:d7u6HkTYKSv5m6MCKkOQlHwaShTMl3HjqSGW3XtVhXM=
|
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e/go.mod h1:d7u6HkTYKSv5m6MCKkOQlHwaShTMl3HjqSGW3XtVhXM=
|
||||||
github.com/tink-crypto/tink-go/v2 v2.1.0 h1:QXFBguwMwTIaU17EgZpEJWsUSc60b1BAGTzBIoMdmok=
|
github.com/tink-crypto/tink-go/v2 v2.1.0 h1:QXFBguwMwTIaU17EgZpEJWsUSc60b1BAGTzBIoMdmok=
|
||||||
github.com/tink-crypto/tink-go/v2 v2.1.0/go.mod h1:y1TnYFt1i2eZVfx4OGc+C+EMp4CoKWAw2VSEuoicHHI=
|
github.com/tink-crypto/tink-go/v2 v2.1.0/go.mod h1:y1TnYFt1i2eZVfx4OGc+C+EMp4CoKWAw2VSEuoicHHI=
|
||||||
github.com/u-root/u-root v0.12.0 h1:K0AuBFriwr0w/PGS3HawiAw89e3+MU7ks80GpghAsNs=
|
github.com/u-root/u-root v0.14.0 h1:Ka4T10EEML7dQ5XDvO9c3MBN8z4nuSnGjcd1jmU2ivg=
|
||||||
github.com/u-root/u-root v0.12.0/go.mod h1:FYjTOh4IkIZHhjsd17lb8nYW6udgXdJhG1c0r6u0arI=
|
github.com/u-root/u-root v0.14.0/go.mod h1:hAyZorapJe4qzbLWlAkmSVCJGbfoU9Pu4jpJ1WMluqE=
|
||||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
||||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
||||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||||
@ -555,8 +566,8 @@ go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCRE
|
|||||||
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
||||||
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
|
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
|
||||||
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
|
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||||
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
||||||
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
||||||
go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI=
|
go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI=
|
||||||
@ -576,8 +587,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||||
@ -593,8 +604,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@ -607,11 +618,11 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||||
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@ -620,8 +631,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@ -669,8 +680,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||||
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
|
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
|
||||||
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@ -684,8 +695,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
|
|||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||||
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@ -699,17 +710,17 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
|
|||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||||
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
|
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||||
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@ -726,10 +737,10 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
|
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||||
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||||
gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k=
|
gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k=
|
||||||
@ -766,9 +777,9 @@ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
|||||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
|
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
|
||||||
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||||
tailscale.com v1.83.0-pre.0.20250331211809-96fe8a6db6c9 h1:mPTb8dGYSqzJhrrYNrLVP717Nh8DME85DWnhBATB/94=
|
tailscale.com v1.84.2 h1:v6aM4RWUgYiV52LRAx6ET+dlGnvO/5lnqPXb7/pMnR0=
|
||||||
tailscale.com v1.83.0-pre.0.20250331211809-96fe8a6db6c9/go.mod h1:iU6kohVzG+bP0/5XjqBAnW8/6nSG/Du++bO+x7VJZD0=
|
tailscale.com v1.84.2/go.mod h1:6/S63NMAhmncYT/1zIPDJkvCuZwMw+JnUuOfSPNazpo=
|
||||||
zgo.at/zcache/v2 v2.1.0 h1:USo+ubK+R4vtjw4viGzTe/zjXyPw6R7SK/RL3epBBxs=
|
zgo.at/zcache/v2 v2.2.0 h1:K29/IPjMniZfveYE+IRXfrl11tMzHkIPuyGrfVZ2fGo=
|
||||||
zgo.at/zcache/v2 v2.1.0/go.mod h1:gyCeoLVo01QjDZynjime8xUGHHMbsLiPyUTBpDGd4Gk=
|
zgo.at/zcache/v2 v2.2.0/go.mod h1:gyCeoLVo01QjDZynjime8xUGHHMbsLiPyUTBpDGd4Gk=
|
||||||
zombiezen.com/go/postgrestest v1.0.1 h1:aXoADQAJmZDU3+xilYVut0pHhgc0sF8ZspPW9gFNwP4=
|
zombiezen.com/go/postgrestest v1.0.1 h1:aXoADQAJmZDU3+xilYVut0pHhgc0sF8ZspPW9gFNwP4=
|
||||||
zombiezen.com/go/postgrestest v1.0.1/go.mod h1:marlZezr+k2oSJrvXHnZUs1olHqpE9czlz8ZYkVxliQ=
|
zombiezen.com/go/postgrestest v1.0.1/go.mod h1:marlZezr+k2oSJrvXHnZUs1olHqpE9czlz8ZYkVxliQ=
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/cenkalti/backoff/v4"
|
"github.com/cenkalti/backoff/v5"
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
@ -95,13 +96,13 @@ func (e *ExtraRecordsMan) Run() {
|
|||||||
// If a file is removed or renamed, fsnotify will loose track of it
|
// If a file is removed or renamed, fsnotify will loose track of it
|
||||||
// and not watch it. We will therefore attempt to re-add it with a backoff.
|
// and not watch it. We will therefore attempt to re-add it with a backoff.
|
||||||
case fsnotify.Remove, fsnotify.Rename:
|
case fsnotify.Remove, fsnotify.Rename:
|
||||||
err := backoff.Retry(func() error {
|
_, err := backoff.Retry(context.Background(), func() (struct{}, error) {
|
||||||
if _, err := os.Stat(e.path); err != nil {
|
if _, err := os.Stat(e.path); err != nil {
|
||||||
return err
|
return struct{}{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return struct{}{}, nil
|
||||||
}, backoff.NewExponentialBackOff())
|
}, backoff.WithBackOff(backoff.NewExponentialBackOff()))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Caller().Err(err).Msgf("extra records filewatcher retrying to find file after delete")
|
log.Error().Caller().Err(err).Msgf("extra records filewatcher retrying to find file after delete")
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/puzpuzpuz/xsync/v3"
|
"github.com/puzpuzpuz/xsync/v4"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/juanfont/headscale/hscontrol/types"
|
"github.com/juanfont/headscale/hscontrol/types"
|
||||||
"github.com/puzpuzpuz/xsync/v3"
|
"github.com/puzpuzpuz/xsync/v4"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/sasha-s/go-deadlock"
|
"github.com/sasha-s/go-deadlock"
|
||||||
"tailscale.com/envknob"
|
"tailscale.com/envknob"
|
||||||
|
@ -1,44 +1,65 @@
|
|||||||
package dockertestutil
|
package dockertestutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ory/dockertest/v3/docker"
|
"github.com/juanfont/headscale/hscontrol/util"
|
||||||
|
"github.com/ory/dockertest/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GetIntegrationRunID returns the run ID for the current integration test session.
|
||||||
|
// This is set by the hi tool and passed through environment variables.
|
||||||
|
func GetIntegrationRunID() string {
|
||||||
|
return os.Getenv("HEADSCALE_INTEGRATION_RUN_ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DockerAddIntegrationLabels adds integration test labels to Docker RunOptions.
|
||||||
|
// This allows the hi tool to identify containers belonging to specific test runs.
|
||||||
|
// This function should be called before passing RunOptions to dockertest functions.
|
||||||
|
func DockerAddIntegrationLabels(opts *dockertest.RunOptions, testType string) {
|
||||||
|
runID := GetIntegrationRunID()
|
||||||
|
if runID == "" {
|
||||||
|
panic("HEADSCALE_INTEGRATION_RUN_ID environment variable is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Labels == nil {
|
||||||
|
opts.Labels = make(map[string]string)
|
||||||
|
}
|
||||||
|
opts.Labels["hi.run-id"] = runID
|
||||||
|
opts.Labels["hi.test-type"] = testType
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateRunID creates a unique run identifier with timestamp and random hash.
|
||||||
|
// Format: YYYYMMDD-HHMMSS-HASH (e.g., 20250619-143052-a1b2c3)
|
||||||
|
func GenerateRunID() string {
|
||||||
|
now := time.Now()
|
||||||
|
timestamp := now.Format("20060102-150405")
|
||||||
|
|
||||||
|
// Add a short random hash to ensure uniqueness
|
||||||
|
randomHash := util.MustGenerateRandomStringDNSSafe(6)
|
||||||
|
return fmt.Sprintf("%s-%s", timestamp, randomHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractRunIDFromContainerName extracts the run ID from container name.
|
||||||
|
// Expects format: "prefix-YYYYMMDD-HHMMSS-HASH"
|
||||||
|
func ExtractRunIDFromContainerName(containerName string) string {
|
||||||
|
parts := strings.Split(containerName, "-")
|
||||||
|
if len(parts) >= 3 {
|
||||||
|
// Return the last three parts as the run ID (YYYYMMDD-HHMMSS-HASH)
|
||||||
|
return strings.Join(parts[len(parts)-3:], "-")
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(fmt.Sprintf("unexpected container name format: %s", containerName))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRunningInContainer checks if the current process is running inside a Docker container.
|
||||||
|
// This is used by tests to determine if they should run integration tests.
|
||||||
func IsRunningInContainer() bool {
|
func IsRunningInContainer() bool {
|
||||||
if _, err := os.Stat("/.dockerenv"); err != nil {
|
// Check for the common indicator that we're in a container
|
||||||
return false
|
// This could be improved with more robust detection if needed
|
||||||
}
|
_, err := os.Stat("/.dockerenv")
|
||||||
|
return err == nil
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func DockerRestartPolicy(config *docker.HostConfig) {
|
|
||||||
// set AutoRemove to true so that stopped container goes away by itself on error *immediately*.
|
|
||||||
// when set to false, containers remain until the end of the integration test.
|
|
||||||
config.AutoRemove = false
|
|
||||||
config.RestartPolicy = docker.RestartPolicy{
|
|
||||||
Name: "no",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DockerAllowLocalIPv6(config *docker.HostConfig) {
|
|
||||||
if config.Sysctls == nil {
|
|
||||||
config.Sysctls = make(map[string]string, 1)
|
|
||||||
}
|
|
||||||
config.Sysctls["net.ipv6.conf.all.disable_ipv6"] = "0"
|
|
||||||
}
|
|
||||||
|
|
||||||
func DockerAllowNetworkAdministration(config *docker.HostConfig) {
|
|
||||||
// Needed since containerd (1.7.24)
|
|
||||||
// https://github.com/tailscale/tailscale/issues/14256
|
|
||||||
// https://github.com/opencontainers/runc/commit/2ce40b6ad72b4bd4391380cafc5ef1bad1fa0b31
|
|
||||||
config.CapAdd = append(config.CapAdd, "NET_ADMIN")
|
|
||||||
config.CapAdd = append(config.CapAdd, "NET_RAW")
|
|
||||||
config.Devices = append(config.Devices, docker.Device{
|
|
||||||
PathOnHost: "/dev/net/tun",
|
|
||||||
PathInContainer: "/dev/net/tun",
|
|
||||||
CgroupPermissions: "rwm",
|
|
||||||
})
|
|
||||||
}
|
}
|
@ -126,3 +126,24 @@ func CleanImagesInCI(pool *dockertest.Pool) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DockerRestartPolicy sets the restart policy for containers.
|
||||||
|
func DockerRestartPolicy(config *docker.HostConfig) {
|
||||||
|
config.RestartPolicy = docker.RestartPolicy{
|
||||||
|
Name: "unless-stopped",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DockerAllowLocalIPv6 allows IPv6 traffic within the container.
|
||||||
|
func DockerAllowLocalIPv6(config *docker.HostConfig) {
|
||||||
|
config.NetworkMode = "default"
|
||||||
|
config.Sysctls = map[string]string{
|
||||||
|
"net.ipv6.conf.all.disable_ipv6": "0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DockerAllowNetworkAdministration gives the container network administration capabilities.
|
||||||
|
func DockerAllowNetworkAdministration(config *docker.HostConfig) {
|
||||||
|
config.CapAdd = append(config.CapAdd, "NET_ADMIN")
|
||||||
|
config.Privileged = true
|
||||||
|
}
|
||||||
|
@ -159,6 +159,7 @@ func New(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if dsic.workdir != "" {
|
if dsic.workdir != "" {
|
||||||
runOptions.WorkingDir = dsic.workdir
|
runOptions.WorkingDir = dsic.workdir
|
||||||
}
|
}
|
||||||
@ -189,6 +190,9 @@ func New(
|
|||||||
Value: "v" + version,
|
Value: "v" + version,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// Add integration test labels if running under hi tool
|
||||||
|
dockertestutil.DockerAddIntegrationLabels(runOptions, "derp")
|
||||||
|
|
||||||
container, err = pool.BuildAndRunWithBuildOptions(
|
container, err = pool.BuildAndRunWithBuildOptions(
|
||||||
buildOptions,
|
buildOptions,
|
||||||
runOptions,
|
runOptions,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package hsic
|
package hsic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"bytes"
|
||||||
"cmp"
|
"cmp"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -12,6 +14,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -311,18 +314,22 @@ func New(
|
|||||||
hsic.env["HEADSCALE_DATABASE_POSTGRES_NAME"] = "headscale"
|
hsic.env["HEADSCALE_DATABASE_POSTGRES_NAME"] = "headscale"
|
||||||
delete(hsic.env, "HEADSCALE_DATABASE_SQLITE_PATH")
|
delete(hsic.env, "HEADSCALE_DATABASE_SQLITE_PATH")
|
||||||
|
|
||||||
pg, err := pool.RunWithOptions(
|
pgRunOptions := &dockertest.RunOptions{
|
||||||
&dockertest.RunOptions{
|
Name: fmt.Sprintf("postgres-%s", hash),
|
||||||
Name: fmt.Sprintf("postgres-%s", hash),
|
Repository: "postgres",
|
||||||
Repository: "postgres",
|
Tag: "latest",
|
||||||
Tag: "latest",
|
Networks: networks,
|
||||||
Networks: networks,
|
Env: []string{
|
||||||
Env: []string{
|
"POSTGRES_USER=headscale",
|
||||||
"POSTGRES_USER=headscale",
|
"POSTGRES_PASSWORD=headscale",
|
||||||
"POSTGRES_PASSWORD=headscale",
|
"POSTGRES_DB=headscale",
|
||||||
"POSTGRES_DB=headscale",
|
},
|
||||||
},
|
}
|
||||||
})
|
|
||||||
|
// Add integration test labels if running under hi tool
|
||||||
|
dockertestutil.DockerAddIntegrationLabels(pgRunOptions, "postgres")
|
||||||
|
|
||||||
|
pg, err := pool.RunWithOptions(pgRunOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("starting postgres container: %w", err)
|
return nil, fmt.Errorf("starting postgres container: %w", err)
|
||||||
}
|
}
|
||||||
@ -366,6 +373,7 @@ func New(
|
|||||||
Env: env,
|
Env: env,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if len(hsic.hostPortBindings) > 0 {
|
if len(hsic.hostPortBindings) > 0 {
|
||||||
runOptions.PortBindings = map[docker.Port][]docker.PortBinding{}
|
runOptions.PortBindings = map[docker.Port][]docker.PortBinding{}
|
||||||
for port, hostPorts := range hsic.hostPortBindings {
|
for port, hostPorts := range hsic.hostPortBindings {
|
||||||
@ -386,6 +394,9 @@ func New(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add integration test labels if running under hi tool
|
||||||
|
dockertestutil.DockerAddIntegrationLabels(runOptions, "headscale")
|
||||||
|
|
||||||
container, err := pool.BuildAndRunWithBuildOptions(
|
container, err := pool.BuildAndRunWithBuildOptions(
|
||||||
headscaleBuildOptions,
|
headscaleBuildOptions,
|
||||||
runOptions,
|
runOptions,
|
||||||
@ -553,22 +564,67 @@ func (t *HeadscaleInContainer) SaveMetrics(savePath string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extractTarToDirectory extracts a tar archive to a directory.
|
||||||
|
func extractTarToDirectory(tarData []byte, targetDir string) error {
|
||||||
|
if err := os.MkdirAll(targetDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create directory %s: %w", targetDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tarReader := tar.NewReader(bytes.NewReader(tarData))
|
||||||
|
for {
|
||||||
|
header, err := tarReader.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read tar header: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean the path to prevent directory traversal
|
||||||
|
cleanName := filepath.Clean(header.Name)
|
||||||
|
if strings.Contains(cleanName, "..") {
|
||||||
|
continue // Skip potentially dangerous paths
|
||||||
|
}
|
||||||
|
|
||||||
|
targetPath := filepath.Join(targetDir, filepath.Base(cleanName))
|
||||||
|
|
||||||
|
switch header.Typeflag {
|
||||||
|
case tar.TypeDir:
|
||||||
|
// Create directory
|
||||||
|
if err := os.MkdirAll(targetPath, os.FileMode(header.Mode)); err != nil {
|
||||||
|
return fmt.Errorf("failed to create directory %s: %w", targetPath, err)
|
||||||
|
}
|
||||||
|
case tar.TypeReg:
|
||||||
|
// Create file
|
||||||
|
outFile, err := os.Create(targetPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create file %s: %w", targetPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.Copy(outFile, tarReader); err != nil {
|
||||||
|
outFile.Close()
|
||||||
|
return fmt.Errorf("failed to copy file contents: %w", err)
|
||||||
|
}
|
||||||
|
outFile.Close()
|
||||||
|
|
||||||
|
// Set file permissions
|
||||||
|
if err := os.Chmod(targetPath, os.FileMode(header.Mode)); err != nil {
|
||||||
|
return fmt.Errorf("failed to set file permissions: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (t *HeadscaleInContainer) SaveProfile(savePath string) error {
|
func (t *HeadscaleInContainer) SaveProfile(savePath string) error {
|
||||||
tarFile, err := t.FetchPath("/tmp/profile")
|
tarFile, err := t.FetchPath("/tmp/profile")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile(
|
targetDir := path.Join(savePath, t.hostname+"-pprof")
|
||||||
path.Join(savePath, t.hostname+".pprof.tar"),
|
return extractTarToDirectory(tarFile, targetDir)
|
||||||
tarFile,
|
|
||||||
os.ModePerm,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *HeadscaleInContainer) SaveMapResponses(savePath string) error {
|
func (t *HeadscaleInContainer) SaveMapResponses(savePath string) error {
|
||||||
@ -577,34 +633,101 @@ func (t *HeadscaleInContainer) SaveMapResponses(savePath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile(
|
targetDir := path.Join(savePath, t.hostname+"-mapresponses")
|
||||||
path.Join(savePath, t.hostname+".maps.tar"),
|
return extractTarToDirectory(tarFile, targetDir)
|
||||||
tarFile,
|
|
||||||
os.ModePerm,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *HeadscaleInContainer) SaveDatabase(savePath string) error {
|
func (t *HeadscaleInContainer) SaveDatabase(savePath string) error {
|
||||||
|
// If using PostgreSQL, skip database file extraction
|
||||||
|
if t.postgres {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, let's see what files are actually in /tmp
|
||||||
|
tmpListing, err := t.Execute([]string{"ls", "-la", "/tmp/"})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Warning: could not list /tmp directory: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Printf("Contents of /tmp in container %s:\n%s", t.hostname, tmpListing)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check for any .sqlite files
|
||||||
|
sqliteFiles, err := t.Execute([]string{"find", "/tmp", "-name", "*.sqlite*", "-type", "f"})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Warning: could not find sqlite files: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Printf("SQLite files found in %s:\n%s", t.hostname, sqliteFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the database file exists and has a schema
|
||||||
|
dbPath := "/tmp/integration_test_db.sqlite3"
|
||||||
|
fileInfo, err := t.Execute([]string{"ls", "-la", dbPath})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("database file does not exist at %s: %w", dbPath, err)
|
||||||
|
}
|
||||||
|
log.Printf("Database file info: %s", fileInfo)
|
||||||
|
|
||||||
|
// Check if the database has any tables (schema)
|
||||||
|
schemaCheck, err := t.Execute([]string{"sqlite3", dbPath, ".schema"})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to check database schema (sqlite3 command failed): %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(schemaCheck) == "" {
|
||||||
|
return fmt.Errorf("database file exists but has no schema (empty database)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show a preview of the schema (first 500 chars)
|
||||||
|
schemaPreview := schemaCheck
|
||||||
|
if len(schemaPreview) > 500 {
|
||||||
|
schemaPreview = schemaPreview[:500] + "..."
|
||||||
|
}
|
||||||
|
log.Printf("Database schema preview:\n%s", schemaPreview)
|
||||||
|
|
||||||
tarFile, err := t.FetchPath("/tmp/integration_test_db.sqlite3")
|
tarFile, err := t.FetchPath("/tmp/integration_test_db.sqlite3")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("failed to fetch database file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile(
|
// For database, extract the first regular file (should be the SQLite file)
|
||||||
path.Join(savePath, t.hostname+".db.tar"),
|
tarReader := tar.NewReader(bytes.NewReader(tarFile))
|
||||||
tarFile,
|
for {
|
||||||
os.ModePerm,
|
header, err := tarReader.Next()
|
||||||
)
|
if err == io.EOF {
|
||||||
if err != nil {
|
break
|
||||||
return err
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read tar header: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Found file in tar: %s (type: %d, size: %d)", header.Name, header.Typeflag, header.Size)
|
||||||
|
|
||||||
|
// Extract the first regular file we find
|
||||||
|
if header.Typeflag == tar.TypeReg {
|
||||||
|
dbPath := path.Join(savePath, t.hostname+".db")
|
||||||
|
outFile, err := os.Create(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create database file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
written, err := io.Copy(outFile, tarReader)
|
||||||
|
outFile.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to copy database file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Extracted database file: %s (%d bytes written, header claimed %d bytes)", dbPath, written, header.Size)
|
||||||
|
|
||||||
|
// Check if we actually wrote something
|
||||||
|
if written == 0 {
|
||||||
|
return fmt.Errorf("database file is empty (size: %d, header size: %d)", written, header.Size)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return fmt.Errorf("no regular file found in database tar archive")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute runs a command inside the Headscale container and returns the
|
// Execute runs a command inside the Headscale container and returns the
|
||||||
|
@ -32,7 +32,7 @@ import (
|
|||||||
"github.com/oauth2-proxy/mockoidc"
|
"github.com/oauth2-proxy/mockoidc"
|
||||||
"github.com/ory/dockertest/v3"
|
"github.com/ory/dockertest/v3"
|
||||||
"github.com/ory/dockertest/v3/docker"
|
"github.com/ory/dockertest/v3/docker"
|
||||||
"github.com/puzpuzpuz/xsync/v3"
|
"github.com/puzpuzpuz/xsync/v4"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -1102,6 +1102,7 @@ func (s *Scenario) runMockOIDC(accessTTL time.Duration, users []mockoidc.MockUse
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
headscaleBuildOptions := &dockertest.BuildOptions{
|
headscaleBuildOptions := &dockertest.BuildOptions{
|
||||||
Dockerfile: hsic.IntegrationTestDockerFileName,
|
Dockerfile: hsic.IntegrationTestDockerFileName,
|
||||||
ContextDir: dockerContextPath,
|
ContextDir: dockerContextPath,
|
||||||
@ -1114,6 +1115,9 @@ func (s *Scenario) runMockOIDC(accessTTL time.Duration, users []mockoidc.MockUse
|
|||||||
|
|
||||||
s.mockOIDC = scenarioOIDC{}
|
s.mockOIDC = scenarioOIDC{}
|
||||||
|
|
||||||
|
// Add integration test labels if running under hi tool
|
||||||
|
dockertestutil.DockerAddIntegrationLabels(mockOidcOptions, "oidc")
|
||||||
|
|
||||||
if pmockoidc, err := s.pool.BuildAndRunWithBuildOptions(
|
if pmockoidc, err := s.pool.BuildAndRunWithBuildOptions(
|
||||||
headscaleBuildOptions,
|
headscaleBuildOptions,
|
||||||
mockOidcOptions,
|
mockOidcOptions,
|
||||||
@ -1198,6 +1202,9 @@ func Webservice(s *Scenario, networkName string) (*dockertest.Resource, error) {
|
|||||||
Env: []string{},
|
Env: []string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add integration test labels if running under hi tool
|
||||||
|
dockertestutil.DockerAddIntegrationLabels(webOpts, "web")
|
||||||
|
|
||||||
webBOpts := &dockertest.BuildOptions{
|
webBOpts := &dockertest.BuildOptions{
|
||||||
Dockerfile: hsic.IntegrationTestDockerFileName,
|
Dockerfile: hsic.IntegrationTestDockerFileName,
|
||||||
ContextDir: dockerContextPath,
|
ContextDir: dockerContextPath,
|
||||||
|
@ -17,7 +17,9 @@ import (
|
|||||||
func isSSHNoAccessStdError(stderr string) bool {
|
func isSSHNoAccessStdError(stderr string) bool {
|
||||||
return strings.Contains(stderr, "Permission denied (tailscale)") ||
|
return strings.Contains(stderr, "Permission denied (tailscale)") ||
|
||||||
// Since https://github.com/tailscale/tailscale/pull/14853
|
// Since https://github.com/tailscale/tailscale/pull/14853
|
||||||
strings.Contains(stderr, "failed to evaluate SSH policy")
|
strings.Contains(stderr, "failed to evaluate SSH policy") ||
|
||||||
|
// Since https://github.com/tailscale/tailscale/pull/16127
|
||||||
|
strings.Contains(stderr, "tailnet policy does not permit you to SSH to this node")
|
||||||
}
|
}
|
||||||
|
|
||||||
var retry = func(times int, sleepInterval time.Duration,
|
var retry = func(times int, sleepInterval time.Duration,
|
||||||
|
@ -251,6 +251,7 @@ func New(
|
|||||||
Env: []string{},
|
Env: []string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if tsic.withWebsocketDERP {
|
if tsic.withWebsocketDERP {
|
||||||
if version != VersionHead {
|
if version != VersionHead {
|
||||||
return tsic, errInvalidClientConfig
|
return tsic, errInvalidClientConfig
|
||||||
@ -279,6 +280,9 @@ func New(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add integration test labels if running under hi tool
|
||||||
|
dockertestutil.DockerAddIntegrationLabels(tailscaleOptions, "tailscale")
|
||||||
|
|
||||||
var container *dockertest.Resource
|
var container *dockertest.Resource
|
||||||
|
|
||||||
if version != VersionHead {
|
if version != VersionHead {
|
||||||
|
@ -3,6 +3,7 @@ package integration
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@ -11,7 +12,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cenkalti/backoff/v4"
|
"github.com/cenkalti/backoff/v5"
|
||||||
policyv2 "github.com/juanfont/headscale/hscontrol/policy/v2"
|
policyv2 "github.com/juanfont/headscale/hscontrol/policy/v2"
|
||||||
"github.com/juanfont/headscale/hscontrol/util"
|
"github.com/juanfont/headscale/hscontrol/util"
|
||||||
"github.com/juanfont/headscale/integration/tsic"
|
"github.com/juanfont/headscale/integration/tsic"
|
||||||
@ -310,20 +311,18 @@ func assertValidNetcheck(t *testing.T, client TailscaleClient) {
|
|||||||
func assertCommandOutputContains(t *testing.T, c TailscaleClient, command []string, contains string) {
|
func assertCommandOutputContains(t *testing.T, c TailscaleClient, command []string, contains string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
err := backoff.Retry(func() error {
|
_, err := backoff.Retry(context.Background(), func() (struct{}, error) {
|
||||||
stdout, stderr, err := c.Execute(command)
|
stdout, stderr, err := c.Execute(command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("executing command, stdout: %q stderr: %q, err: %w", stdout, stderr, err)
|
return struct{}{}, fmt.Errorf("executing command, stdout: %q stderr: %q, err: %w", stdout, stderr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(stdout, contains) {
|
if !strings.Contains(stdout, contains) {
|
||||||
return fmt.Errorf("executing command, expected string %q not found in %q", contains, stdout)
|
return struct{}{}, fmt.Errorf("executing command, expected string %q not found in %q", contains, stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return struct{}{}, nil
|
||||||
}, backoff.NewExponentialBackOff(
|
}, backoff.WithBackOff(backoff.NewExponentialBackOff()), backoff.WithMaxElapsedTime(10*time.Second))
|
||||||
backoff.WithMaxElapsedTime(10*time.Second)),
|
|
||||||
)
|
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user