mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-25 10:18:59 +02:00
Compare commits
15 Commits
compat
...
feat/rules
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95ffd35585 | ||
|
|
7b0d846576 | ||
|
|
458c7779d3 | ||
|
|
dc6c649f2c | ||
|
|
3c5c3ecac2 | ||
|
|
a94442b001 | ||
|
|
2a51c2ef52 | ||
|
|
6477c35b15 | ||
|
|
5b20bbeb6f | ||
|
|
5ba475c489 | ||
|
|
54be056530 | ||
|
|
08de9086c3 | ||
|
|
1a17f3943a | ||
|
|
9bb5c54e7c | ||
|
|
faecbab2cb |
@@ -1 +0,0 @@
|
||||
**/node_modules
|
||||
@@ -58,12 +58,8 @@ GODOXY_API_ADDR=127.0.0.1:8888
|
||||
|
||||
# Local API listening address (unauthenticated, optional)
|
||||
# Useful for local development, debugging or automation
|
||||
# Must bind to loopback only (localhost / 127.0.0.1 / ::1) unless GODOXY_LOCAL_API_ALLOW_NON_LOOPBACK is true
|
||||
GODOXY_LOCAL_API_ADDR=
|
||||
|
||||
# WARNING: exposing local API to either LAN or WAN is dangerous, do not enable this unless you know what you're doing
|
||||
GODOXY_LOCAL_API_ALLOW_NON_LOOPBACK=false
|
||||
|
||||
# Metrics
|
||||
GODOXY_METRICS_DISABLE_CPU=false
|
||||
GODOXY_METRICS_DISABLE_MEMORY=false
|
||||
|
||||
10
.github/workflows/cli-binary.yml
vendored
10
.github/workflows/cli-binary.yml
vendored
@@ -47,20 +47,14 @@ jobs:
|
||||
|
||||
- name: Build CLI
|
||||
run: |
|
||||
make cli=1 NAME=${{ matrix.binary_name }} build
|
||||
make CLI_BIN_PATH=bin/${{ matrix.binary_name }} build-cli
|
||||
|
||||
- name: Check binary
|
||||
run: |
|
||||
file bin/${{ matrix.binary_name }}
|
||||
|
||||
- name: Upload
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.binary_name }}
|
||||
path: bin/${{ matrix.binary_name }}
|
||||
|
||||
- name: Upload to release
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
files: bin/${{ matrix.binary_name }}
|
||||
|
||||
25
.github/workflows/docker-image-compat.yml
vendored
25
.github/workflows/docker-image-compat.yml
vendored
@@ -1,25 +0,0 @@
|
||||
name: Docker Image CI (nightly)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "*" # matches every branch that doesn't contain a '/'
|
||||
- "*/*" # matches every branch containing a single '/'
|
||||
- "**" # matches every branch
|
||||
- "!dependabot/*"
|
||||
- "!main" # excludes main
|
||||
- "!compat" # excludes compat branch
|
||||
|
||||
jobs:
|
||||
build-nightly:
|
||||
uses: ./.github/workflows/docker-image.yml
|
||||
with:
|
||||
image_name: ${{ github.repository_owner }}/godoxy
|
||||
tag: nightly
|
||||
target: main
|
||||
build-nightly-agent:
|
||||
uses: ./.github/workflows/docker-image.yml
|
||||
with:
|
||||
image_name: ${{ github.repository_owner }}/godoxy-agent
|
||||
tag: nightly
|
||||
target: agent
|
||||
16
.github/workflows/docker-image-nightly.yml
vendored
16
.github/workflows/docker-image-nightly.yml
vendored
@@ -1,20 +1,24 @@
|
||||
name: Docker Image CI (compat)
|
||||
name: Docker Image CI (nightly)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "compat" # compat branch
|
||||
- "*" # matches every branch that doesn't contain a '/'
|
||||
- "*/*" # matches every branch containing a single '/'
|
||||
- "**" # matches every branch
|
||||
- "!dependabot/*"
|
||||
- "!main" # excludes main
|
||||
|
||||
jobs:
|
||||
build-compat:
|
||||
build-nightly:
|
||||
uses: ./.github/workflows/docker-image.yml
|
||||
with:
|
||||
image_name: ${{ github.repository_owner }}/godoxy
|
||||
tag: compat
|
||||
tag: nightly
|
||||
target: main
|
||||
build-compat-agent:
|
||||
build-nightly-agent:
|
||||
uses: ./.github/workflows/docker-image.yml
|
||||
with:
|
||||
image_name: ${{ github.repository_owner }}/godoxy-agent
|
||||
tag: compat
|
||||
tag: nightly
|
||||
target: agent
|
||||
|
||||
8
.github/workflows/docker-image.yml
vendored
8
.github/workflows/docker-image.yml
vendored
@@ -78,7 +78,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ inputs.image_name }}
|
||||
tags: |
|
||||
@@ -86,12 +86,12 @@ jobs:
|
||||
type=ref,event=tag
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
platforms: ${{ matrix.platform }}
|
||||
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v7
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
21
.github/workflows/merge-main-into-compat.yml
vendored
21
.github/workflows/merge-main-into-compat.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Refresh Compat from Main Patch
|
||||
name: Cherry-pick into Compat
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
- ".github/workflows/merge-main-into-compat.yml"
|
||||
|
||||
jobs:
|
||||
refresh-compat:
|
||||
cherry-pick:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -20,9 +20,20 @@ jobs:
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
- name: Refresh compat with single patch commit
|
||||
- name: Cherry-pick commits from last tag
|
||||
run: |
|
||||
./scripts/refresh-compat.sh
|
||||
git fetch origin compat
|
||||
git checkout compat
|
||||
CURRENT_TAG=${{ github.ref_name }}
|
||||
PREV_TAG=$(git describe --tags --abbrev=0 $CURRENT_TAG^ 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$PREV_TAG" ]; then
|
||||
echo "No previous tag found. Cherry-picking all commits up to $CURRENT_TAG"
|
||||
git rev-list --reverse --no-merges $CURRENT_TAG | xargs -r git cherry-pick
|
||||
else
|
||||
echo "Cherry-picking commits from $PREV_TAG to $CURRENT_TAG"
|
||||
git rev-list --reverse --no-merges $PREV_TAG..$CURRENT_TAG | xargs -r git cherry-pick
|
||||
fi
|
||||
- name: Push compat
|
||||
run: |
|
||||
git push origin compat --force
|
||||
git push origin compat
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -39,5 +39,8 @@ CLAUDE.md
|
||||
|
||||
!.trunk/configs
|
||||
|
||||
# minified files
|
||||
**/*-min.*
|
||||
|
||||
# generated CLI commands
|
||||
cmd/cli/generated_commands.go
|
||||
30
AGENTS.md
30
AGENTS.md
@@ -1,30 +0,0 @@
|
||||
# AGENTS.md
|
||||
|
||||
## Principles
|
||||
|
||||
DO NOT run build command.
|
||||
|
||||
## Documentation
|
||||
|
||||
Update package level `README.md` after making significant changes.
|
||||
|
||||
## Go Guidelines
|
||||
|
||||
1. Use `golang-best-practices` skill.
|
||||
2. Use `internal/task/task.go` for lifetime management:
|
||||
- `task.RootTask()` for background operations
|
||||
- `parent.Subtask()` for nested tasks
|
||||
- `OnFinished()` and `OnCancel()` callbacks for cleanup
|
||||
3. Use `gperr "goutils/errs"` to build pretty nested errors:
|
||||
- `gperr.Multiline()` for multiple operation attempts
|
||||
- `gperr.NewBuilder()` to collect errors
|
||||
- `gperr.NewGroup() + group.Go()` to collect errors of multiple concurrent operations
|
||||
- `gperr.PrependSubject()` to prepend subject to errors
|
||||
4. Use `github.com/puzpuzpuz/xsync/v4` for lock-free thread safe maps
|
||||
5. Use `goutils/synk` to retrieve and put byte buffer
|
||||
|
||||
## Testing
|
||||
|
||||
- Prefer scoped tests
|
||||
- Prefer `testify`
|
||||
- Use `-ldflags="-checklinkname=0"`
|
||||
@@ -1,5 +1,5 @@
|
||||
# Stage 1: deps
|
||||
FROM golang:1.26.2-alpine AS deps
|
||||
FROM golang:1.26.0-alpine AS deps
|
||||
HEALTHCHECK NONE
|
||||
|
||||
# package version does not matter
|
||||
@@ -18,9 +18,9 @@ COPY internal/gopsutil/go.mod internal/gopsutil/go.sum ./internal/gopsutil/
|
||||
COPY internal/go-proxmox/go.mod internal/go-proxmox/go.sum ./internal/go-proxmox/
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
# for minify
|
||||
COPY --from=oven/bun:1-alpine /usr/local/bin/bun /usr/local/bin/bun
|
||||
COPY --from=oven/bun:1-alpine /usr/local/bin/bunx /usr/local/bin/bunx
|
||||
# for minify-js
|
||||
COPY --from=oven/bun:1.3.9-alpine /usr/local/bin/bun /usr/local/bin/bun
|
||||
COPY --from=oven/bun:1.3.9-alpine /usr/local/bin/bunx /usr/local/bin/bunx
|
||||
|
||||
# remove godoxy stuff from go.mod first
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
@@ -34,7 +34,6 @@ FROM deps AS builder
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
COPY scripts/minify ./scripts/minify
|
||||
COPY go.mod go.sum ./
|
||||
COPY Makefile ./
|
||||
COPY cmd ./cmd
|
||||
|
||||
64
Makefile
64
Makefile
@@ -6,8 +6,8 @@ export GOOS = linux
|
||||
|
||||
REPO_URL ?= https://github.com/yusing/godoxy
|
||||
|
||||
WEBUI_DIR ?= $(shell pwd)/../godoxy-webui
|
||||
DOCS_DIR ?= ${WEBUI_DIR}/wiki
|
||||
WEBUI_DIR ?= ../godoxy-webui
|
||||
DOCS_DIR ?= wiki
|
||||
|
||||
ifneq ($(BRANCH), compat)
|
||||
GO_TAGS = sonic
|
||||
@@ -17,22 +17,15 @@ endif
|
||||
|
||||
LDFLAGS = -X github.com/yusing/goutils/version.version=${VERSION} -checklinkname=0
|
||||
|
||||
PACKAGE ?= ./cmd
|
||||
|
||||
ifeq ($(agent), 1)
|
||||
NAME = godoxy-agent
|
||||
PWD = ${shell pwd}/agent
|
||||
else ifeq ($(socket-proxy), 1)
|
||||
NAME = godoxy-socket-proxy
|
||||
PWD = ${shell pwd}/socket-proxy
|
||||
else ifeq ($(cli), 1)
|
||||
NAME = godoxy-cli
|
||||
PWD = ${shell pwd}/cmd/cli
|
||||
PACKAGE = .
|
||||
else
|
||||
NAME = godoxy
|
||||
PWD = ${shell pwd}
|
||||
godoxy = 1
|
||||
endif
|
||||
|
||||
ifeq ($(trace), 1)
|
||||
@@ -65,6 +58,7 @@ endif
|
||||
|
||||
BUILD_FLAGS += -tags '$(GO_TAGS)' -ldflags='$(LDFLAGS)'
|
||||
BIN_PATH := $(shell pwd)/bin/${NAME}
|
||||
CLI_BIN_PATH ?= $(shell pwd)/bin/godoxy-cli
|
||||
|
||||
export NAME
|
||||
export CGO_ENABLED
|
||||
@@ -82,11 +76,7 @@ endif
|
||||
|
||||
|
||||
# CAP_NET_BIND_SERVICE: permission for binding to :80 and :443
|
||||
POST_BUILD = echo;
|
||||
|
||||
ifeq ($(godoxy), 1)
|
||||
POST_BUILD += $(SETCAP_CMD) CAP_NET_BIND_SERVICE=+ep ${BIN_PATH};
|
||||
endif
|
||||
POST_BUILD = $(SETCAP_CMD) CAP_NET_BIND_SERVICE=+ep ${BIN_PATH};
|
||||
ifeq ($(docker), 1)
|
||||
POST_BUILD += mkdir -p /app && mv ${BIN_PATH} /app/run;
|
||||
endif
|
||||
@@ -112,45 +102,44 @@ update-go:
|
||||
sed -i 's|FROM golang:.*-alpine|FROM golang:${go_ver}-alpine|g' $$file; \
|
||||
done
|
||||
for path in ${gomod_paths}; do \
|
||||
echo "go mod tidy $$path"; \
|
||||
cd ${PWD}/$$path && go mod tidy; \
|
||||
done
|
||||
|
||||
update-deps:
|
||||
for path in ${gomod_paths}; do \
|
||||
echo "go get -u $$path"; \
|
||||
cd ${PWD}/$$path && go get -u ./... && go mod tidy; \
|
||||
done
|
||||
|
||||
mod-tidy:
|
||||
for path in ${gomod_paths}; do \
|
||||
echo "go mod tidy $$path"; \
|
||||
cd ${PWD}/$$path && go mod tidy; \
|
||||
done
|
||||
|
||||
modernize:
|
||||
for path in ${gomod_paths}; do \
|
||||
cd ${PWD}/$$path && go fix ./...; \
|
||||
done
|
||||
|
||||
minify:
|
||||
minify-js:
|
||||
@if [ "${agent}" = "1" ]; then \
|
||||
echo "minify: skipped for agent"; \
|
||||
echo "minify-js: skipped for agent"; \
|
||||
elif [ "${socket-proxy}" = "1" ]; then \
|
||||
echo "minify: skipped for socket-proxy"; \
|
||||
echo "minify-js: skipped for socket-proxy"; \
|
||||
else \
|
||||
bun --bun scripts/minify; \
|
||||
for file in $$(find internal/ -name '*.js' | grep -v -- '-min\.js$$'); do \
|
||||
ext="$${file##*.}"; \
|
||||
base="$${file%.*}"; \
|
||||
min_file="$${base}-min.$$ext"; \
|
||||
echo "minifying $$file -> $$min_file"; \
|
||||
bunx --bun uglify-js $$file --compress --mangle --output $$min_file; \
|
||||
done \
|
||||
fi
|
||||
|
||||
build:
|
||||
@if [ "${godoxy}" = "1" ]; then \
|
||||
make minify; \
|
||||
elif [ "${cli}" = "1" ]; then \
|
||||
make gen-cli; \
|
||||
fi
|
||||
build: minify-js
|
||||
mkdir -p $(shell dirname ${BIN_PATH})
|
||||
go build -C ${PWD} ${BUILD_FLAGS} -o ${BIN_PATH} ${PACKAGE}
|
||||
go build -C ${PWD} ${BUILD_FLAGS} -o ${BIN_PATH} ./cmd
|
||||
${POST_BUILD}
|
||||
|
||||
run: minify
|
||||
cd ${PWD} && [ -f .env ] && godotenv -f .env go run ${BUILD_FLAGS} ${PACKAGE}
|
||||
run: minify-js
|
||||
cd ${PWD} && [ -f .env ] && godotenv -f .env go run ${BUILD_FLAGS} ./cmd
|
||||
|
||||
dev:
|
||||
docker compose -f dev.compose.yml $(args)
|
||||
@@ -194,13 +183,16 @@ gen-swagger:
|
||||
|
||||
gen-api-types: gen-swagger
|
||||
# --disable-throw-on-error
|
||||
bunx --bun swagger-typescript-api generate --sort-types --generate-union-enums --add-readonly --route-types \
|
||||
bunx --bun swagger-typescript-api generate --sort-types --generate-union-enums --axios --add-readonly --route-types \
|
||||
--responses -o ${WEBUI_DIR}/src/lib -n api.ts -p internal/api/v1/docs/swagger.json
|
||||
|
||||
.PHONY: gen-cli build-cli update-wiki
|
||||
|
||||
gen-cli:
|
||||
cd cmd/cli && go run ./gen
|
||||
|
||||
build-cli: gen-cli
|
||||
mkdir -p $(shell dirname ${CLI_BIN_PATH})
|
||||
go build -C cmd/cli -o ${CLI_BIN_PATH} .
|
||||
|
||||
.PHONY: gen-cli build-cli update-wiki
|
||||
update-wiki:
|
||||
DOCS_DIR=${DOCS_DIR} REPO_URL=${REPO_URL} bun --bun scripts/update-wiki
|
||||
DOCS_DIR=${DOCS_DIR} REPO_URL=${REPO_URL} bun --bun scripts/update-wiki/main.ts
|
||||
|
||||
94
agent/go.mod
94
agent/go.mod
@@ -1,6 +1,11 @@
|
||||
module github.com/yusing/godoxy/agent
|
||||
|
||||
go 1.26.2
|
||||
go 1.26.0
|
||||
|
||||
exclude (
|
||||
github.com/moby/moby/api v1.53.0 // allow older daemon versions
|
||||
github.com/moby/moby/client v0.2.2 // allow older daemon versions
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/shirou/gopsutil/v4 => ../internal/gopsutil
|
||||
@@ -15,105 +20,90 @@ replace (
|
||||
exclude github.com/containerd/nerdctl/mod/tigron v0.0.0
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.12.0
|
||||
github.com/bytedance/sonic v1.15.0
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/pion/dtls/v3 v3.1.2
|
||||
github.com/pion/transport/v3 v3.1.1
|
||||
github.com/rs/zerolog v1.35.1
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/yusing/godoxy v0.28.0
|
||||
github.com/yusing/godoxy v0.26.0
|
||||
github.com/yusing/godoxy/socketproxy v0.0.0-00010101000000-000000000000
|
||||
github.com/yusing/goutils v0.7.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/andybalholm/brotli v1.2.1 // indirect
|
||||
github.com/bytedance/gopkg v0.1.4 // indirect
|
||||
github.com/bytedance/sonic v1.15.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.1 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/cli v29.4.1+incompatible
|
||||
github.com/docker/go-connections v0.7.0
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/ebitengine/purego v0.10.0 // indirect
|
||||
github.com/docker/cli v29.2.1+incompatible // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/ebitengine/purego v0.9.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/gin-contrib/sse v1.1.1 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.30.2 // indirect
|
||||
github.com/goccy/go-json v0.10.6 // indirect
|
||||
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
|
||||
github.com/klauspost/compress v1.18.5 // indirect
|
||||
github.com/klauspost/compress v1.18.4 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.21 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1
|
||||
github.com/moby/moby/api v1.52.0
|
||||
github.com/moby/moby/client v0.2.1
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/moby/api v1.52.0 // indirect
|
||||
github.com/moby/moby/client v0.2.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.3.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pion/logging v0.2.4 // indirect
|
||||
github.com/pion/transport/v4 v4.0.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/puzpuzpuz/xsync/v4 v4.5.0 // indirect
|
||||
github.com/puzpuzpuz/xsync/v4 v4.4.0 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.26.3 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.26.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.70.0 // indirect
|
||||
github.com/valyala/fasthttp v1.69.0 // indirect
|
||||
github.com/yusing/ds v0.4.1 // indirect
|
||||
github.com/yusing/gointernals v0.2.0 // indirect
|
||||
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260423020826-a0d979d1386c // indirect
|
||||
github.com/yusing/goutils/http/websocket v0.0.0-20260423020826-a0d979d1386c // indirect
|
||||
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260218062549-0b0fa3a059ec // indirect
|
||||
github.com/yusing/goutils/http/websocket v0.0.0-20260218062549-0b0fa3a059ec // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.1 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
golang.org/x/arch v0.26.0 // indirect
|
||||
golang.org/x/crypto v0.50.0 // indirect
|
||||
golang.org/x/net v0.53.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect
|
||||
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||
golang.org/x/arch v0.24.0 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/net v0.50.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
exclude (
|
||||
github.com/moby/moby/api v1.53.0
|
||||
github.com/moby/moby/api v1.54.0
|
||||
github.com/moby/moby/api v1.54.1
|
||||
github.com/moby/moby/api v1.54.2
|
||||
)
|
||||
|
||||
exclude (
|
||||
github.com/moby/moby/client v0.2.2
|
||||
github.com/moby/moby/client v0.3.0
|
||||
github.com/moby/moby/client v0.4.0
|
||||
github.com/moby/moby/client v0.4.1
|
||||
)
|
||||
|
||||
199
agent/go.sum
199
agent/go.sum
@@ -1,19 +1,19 @@
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/PuerkitoBio/goquery v1.12.0 h1:pAcL4g3WRXekcB9AU/y1mbKez2dbY2AajVhtkO8RIBo=
|
||||
github.com/PuerkitoBio/goquery v1.12.0/go.mod h1:802ej+gV2y7bbIhOIoPY5sT183ZW0YFofScC4q/hIpQ=
|
||||
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
|
||||
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
|
||||
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY=
|
||||
github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE=
|
||||
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
|
||||
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||
github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI=
|
||||
github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
@@ -24,40 +24,41 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG
|
||||
github.com/containerd/errdefs v1.0.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/coreos/go-oidc/v3 v3.18.0 h1:V9orjXynvu5wiC9SemFTWnG4F45v403aIcjWo0d41+A=
|
||||
github.com/coreos/go-oidc/v3 v3.18.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
|
||||
github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
|
||||
github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/diskfs/go-diskfs v1.9.1 h1:g/UCTC5jZFomhtH4DyF9fG1eRHGgDIjSd1hSjEErXn0=
|
||||
github.com/diskfs/go-diskfs v1.9.1/go.mod h1:rW9+4MPN1tbMpQqRZlcM3YQsh3Ucc+Q1k1iIqzzmZcg=
|
||||
github.com/diskfs/go-diskfs v1.7.0 h1:vonWmt5CMowXwUc79jWyGrf2DIMeoOjkLlMnQYGVOs8=
|
||||
github.com/diskfs/go-diskfs v1.7.0/go.mod h1:LhQyXqOugWFRahYUSw47NyZJPezFzB9UELwhpszLP/k=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||
github.com/docker/cli v29.4.1+incompatible h1:02RT8QqqwtGRn+6SYypv8IUEbD/ltY6sfKCJIoUcGzk=
|
||||
github.com/docker/cli v29.4.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=
|
||||
github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
|
||||
github.com/docker/cli v29.2.1+incompatible h1:n3Jt0QVCN65eiVBoUTZQM9mcQICCJt3akW4pKAbKdJg=
|
||||
github.com/docker/cli v29.2.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
||||
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
|
||||
github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
|
||||
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
||||
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
||||
github.com/go-acme/lego/v4 v4.35.1 h1:xTcEBsENs4Ek5EiNpN3JXy7W5aynf5KhJ6D/HuuhXv4=
|
||||
github.com/go-acme/lego/v4 v4.35.1/go.mod h1:E+4l5mPPg9dwhTz6JJptNLvtj94E2JDObI50QuFJd3s=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||
github.com/go-acme/lego/v4 v4.32.0 h1:z7Ss7aa1noabhKj+DBzhNCO2SM96xhE3b0ucVW3x8Tc=
|
||||
github.com/go-acme/lego/v4 v4.32.0/go.mod h1:lI2fZNdgeM/ymf9xQ9YKbgZm6MeDuf91UrohMQE4DhI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -72,14 +73,15 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ=
|
||||
github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc=
|
||||
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
||||
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
@@ -91,14 +93,14 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gotify/server/v2 v2.9.1 h1:wsQUCdYJ4ZvP7RIRKDLtAtmFQc3kxbrv3QqccO5RWzs=
|
||||
github.com/gotify/server/v2 v2.9.1/go.mod h1:8scw0hiExomp4rJDrXBwRIcgQm7kv74P4Z4B+iM4l8w=
|
||||
github.com/gotify/server/v2 v2.9.0 h1:2zRCl28wkq0oc6YNbyJS2n0dDOOVvOS3Oez5AG2ij54=
|
||||
github.com/gotify/server/v2 v2.9.0/go.mod h1:249wwlUqHTr0QsiKARGtFVqds0pNLIMjYLinHyMACdQ=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU=
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl5BIHNXfS9+C35ZyJaklL7mLDbgUkcgXzSLa8Tk0=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -109,16 +111,19 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
||||
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
||||
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e h1:Q6MvJtQK/iRcRtzAscm/zF23XxJlbECiGPyRicsX+Ak=
|
||||
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||
github.com/luthermonson/go-proxmox v0.4.1 h1:1WnUBHzCQEa5goHuzewkApi6LKtQcFB8/tXTtS2D5w8=
|
||||
github.com/luthermonson/go-proxmox v0.4.1/go.mod h1:U6dAkJ+iiwaeb1g/LMWpWuWN4nmvWeXhmoMuYJMumS4=
|
||||
github.com/magefile/mage v1.17.2 h1:fyXVu1eadI8Ap1HCCNgEhJ5McIWiYhLR8uol64ZZc40=
|
||||
github.com/magefile/mage v1.17.2/go.mod h1:Yj51kqllmsgFpvvSzgrZPK9WtluG3kUhFaBUVLo4feA=
|
||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM=
|
||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||
github.com/luthermonson/go-proxmox v0.4.0 h1:LKXpG9d64zTaQF79wV0kfOnnSwIcdG39m7sc4ga+XZs=
|
||||
github.com/luthermonson/go-proxmox v0.4.0/go.mod h1:U6dAkJ+iiwaeb1g/LMWpWuWN4nmvWeXhmoMuYJMumS4=
|
||||
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
|
||||
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
@@ -130,17 +135,16 @@ github.com/moby/moby/client v0.2.1/go.mod h1:O+/tw5d4a1Ha/ZA/tPxIZJapJRUS6LNZ1wi
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
||||
github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM=
|
||||
github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc=
|
||||
github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo=
|
||||
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
|
||||
@@ -149,29 +153,31 @@ github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkY
|
||||
github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=
|
||||
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
|
||||
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
|
||||
github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=
|
||||
github.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI=
|
||||
github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
|
||||
github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.5.0 h1:vOSWu6b57/emh+L/Cw0BeQfvxa/cogFywXHeGUxQxAg=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.5.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.4.0 h1:vlSN6/CkEY0pY8KaB0yqo/pCLZvp9nhdbBdjipT4gWo=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.4.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI=
|
||||
github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
||||
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
|
||||
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/samber/slog-common v0.22.0 h1:WyPxYRg/c5xUmxZJbtd0QgysHlLBhRA+MngKdJieHxE=
|
||||
github.com/samber/slog-common v0.22.0/go.mod h1:d/6OaSlzdkl9PFpfRLgn8FwY1OW6EFmPtBpsHX4MrU0=
|
||||
github.com/samber/slog-zerolog/v2 v2.9.2 h1:DIFzfzDTxHeRyGlfg/D7b2by7VVzcsBTybRPrzjWF4c=
|
||||
github.com/samber/slog-zerolog/v2 v2.9.2/go.mod h1:2q6cYK2OcN6YfQE/WyCnUtigc+yYf3ozqGsGmRwZR6I=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/samber/slog-common v0.20.0 h1:WaLnm/aCvBJSk5nR5aXZTFBaV0B47A+AEaEOiZDeUnc=
|
||||
github.com/samber/slog-common v0.20.0/go.mod h1:+Ozat1jgnnE59UAlmNX1IF3IByHsODnnwf9jUcBZ+m8=
|
||||
github.com/samber/slog-zerolog/v2 v2.9.1 h1:RMOq8XqzfuGx1X0TEIlS9OXbbFmqLY2/wJppghz66YY=
|
||||
github.com/samber/slog-zerolog/v2 v2.9.1/go.mod h1:DQYYve14WgCRN/XnKeHl4266jXK0DgYkYXkfZ4Fp98k=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
@@ -196,8 +202,8 @@ github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY
|
||||
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.70.0 h1:LAhMGcWk13QZWm85+eg8ZBNbrq5mnkWFGbHMUJHIdXA=
|
||||
github.com/valyala/fasthttp v1.70.0/go.mod h1:oDZEHHkJ/Buyklg6uURmYs19442zFSnCIfX3j1FY3pE=
|
||||
github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI=
|
||||
github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw=
|
||||
github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI=
|
||||
github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
@@ -208,49 +214,50 @@ github.com/yusing/gointernals v0.2.0 h1:jyWB3kdUPkuU6s0r8QY/sS5h2WNBF4Kfisly8dtS
|
||||
github.com/yusing/gointernals v0.2.0/go.mod h1:xGzNbPGMm5Z8kG0t4JYISMscw+gMQlgghkLxlgRZv5Y=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.1 h1:j2U/Qp+wvueSpqitLCSZPT/+ZpVc1xzuwdHWwl7d8ro=
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.1/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
|
||||
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
|
||||
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
golang.org/x/arch v0.26.0 h1:jZ6dpec5haP/fUv1kLCbuJy6dnRrfX6iVK08lZBFpk4=
|
||||
golang.org/x/arch v0.26.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||
golang.org/x/arch v0.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y=
|
||||
golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
||||
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"encoding/json"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/agent/pkg/agent/common"
|
||||
@@ -366,7 +366,7 @@ func (cfg *AgentConfig) fetchJSON(ctx context.Context, endpoint string, out any)
|
||||
return resp.StatusCode, nil
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, out)
|
||||
err = sonic.Unmarshal(data, out)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"encoding/json"
|
||||
"github.com/bytedance/sonic"
|
||||
route "github.com/yusing/godoxy/internal/route/types"
|
||||
)
|
||||
|
||||
@@ -53,7 +53,7 @@ func proxyConfigFromHeaders(h http.Header) (cfg Config, err error) {
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(cfgJSON, &cfg)
|
||||
err = sonic.Unmarshal(cfgJSON, &cfg)
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func (cfg *Config) SetAgentProxyConfigHeadersLegacy(h http.Header) {
|
||||
func (cfg *Config) SetAgentProxyConfigHeaders(h http.Header) {
|
||||
h.Set(HeaderXProxyHost, cfg.Host)
|
||||
h.Set(HeaderXProxyScheme, string(cfg.Scheme))
|
||||
cfgJSON, _ := json.Marshal(cfg.HTTPConfig)
|
||||
cfgJSON, _ := sonic.Marshal(cfg.HTTPConfig)
|
||||
cfgBase64 := base64.StdEncoding.EncodeToString(cfgJSON)
|
||||
h.Set(HeaderXProxyConfig, cfgBase64)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"encoding/json"
|
||||
"github.com/bytedance/sonic"
|
||||
healthcheck "github.com/yusing/godoxy/internal/health/check"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
)
|
||||
@@ -73,7 +73,7 @@ func CheckHealth(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(result)
|
||||
sonic.ConfigDefault.NewEncoder(w).Encode(result)
|
||||
}
|
||||
|
||||
func parseMsOrDefault(msStr string) time.Duration {
|
||||
|
||||
@@ -3,7 +3,7 @@ package handler
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"encoding/json"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||
@@ -51,7 +51,7 @@ func NewAgentHandler() http.Handler {
|
||||
Runtime: env.Runtime,
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(agentInfo)
|
||||
sonic.ConfigDefault.NewEncoder(w).Encode(agentInfo)
|
||||
})
|
||||
mux.HandleEndpoint("GET", agent.EndpointHealth, CheckHealth)
|
||||
mux.HandleEndpoint("GET", agent.EndpointSystemInfo, metricsHandler.ServeHTTP)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.26.2-alpine AS builder
|
||||
FROM golang:1.26.0-alpine AS builder
|
||||
|
||||
HEALTHCHECK NONE
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module github.com/yusing/godoxy/cmd/bench_server
|
||||
|
||||
go 1.26.2
|
||||
go 1.26.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/yusing/godoxy/cli
|
||||
|
||||
go 1.26.2
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"github.com/yusing/godoxy/internal/auth"
|
||||
"github.com/yusing/godoxy/internal/idlewatcher"
|
||||
idlewatcherTypes "github.com/yusing/godoxy/internal/idlewatcher/types"
|
||||
"github.com/yusing/goutils/http/reverseproxy"
|
||||
)
|
||||
|
||||
type debugMux struct {
|
||||
@@ -134,9 +133,6 @@ func listenDebugServer() {
|
||||
})
|
||||
|
||||
mux.HandleFunc("Auth block page", "GET", "/auth/block", AuthBlockPageHandler)
|
||||
mux.HandleFunc("Origin unreachable page", "GET", "/reverseproxy/origin_unreachable", func(w http.ResponseWriter, r *http.Request) {
|
||||
reverseproxy.WriteDebugOriginUnreachablePage(w)
|
||||
})
|
||||
mux.HandleFunc("Idlewatcher loading page", "GET", idlewatcherTypes.PathPrefix, idlewatcher.DebugHandler)
|
||||
apiHandler := newAPIHandler(mux)
|
||||
mux.mux.HandleFunc("/api/v1/", apiHandler.ServeHTTP)
|
||||
@@ -144,9 +140,8 @@ func listenDebugServer() {
|
||||
mux.Finalize()
|
||||
|
||||
go func() {
|
||||
log.Info().Msgf("debug server started at http://localhost:7778/")
|
||||
//nolint:gosec
|
||||
err := http.ListenAndServe(":7778", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
err := http.ListenAndServe(":7777", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
w.Header().Set("Expires", "0")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.26.2-alpine AS builder
|
||||
FROM golang:1.26.0-alpine AS builder
|
||||
|
||||
HEALTHCHECK NONE
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module github.com/yusing/godoxy/cmd/h2c_test_server
|
||||
|
||||
go 1.26.2
|
||||
go 1.26.0
|
||||
|
||||
require golang.org/x/net v0.53.0
|
||||
require golang.org/x/net v0.50.0
|
||||
|
||||
require golang.org/x/text v0.36.0 // indirect
|
||||
require golang.org/x/text v0.34.0 // indirect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
|
||||
@@ -19,17 +19,6 @@
|
||||
|
||||
# 3. other providers, see https://docs.godoxy.dev/DNS-01-Providers
|
||||
|
||||
# Inbound mTLS profiles (optional)
|
||||
#
|
||||
# Reusable named profiles for inbound HTTPS client-certificate validation.
|
||||
# A profile must trust either the system CA store, one or more CA files, or both.
|
||||
#
|
||||
# inbound_mtls_profiles:
|
||||
# corp:
|
||||
# use_system_cas: true
|
||||
# ca_files:
|
||||
# - /app/certs/corp-ca.pem
|
||||
|
||||
# Access Control
|
||||
# When enabled, it will be applied globally at connection level,
|
||||
# all incoming connections (web, tcp and udp) will be checked against the ACL rules.
|
||||
@@ -76,6 +65,7 @@ entrypoint:
|
||||
Access-Control-Allow-Headers: "*"
|
||||
Access-Control-Allow-Origin: "*"
|
||||
Access-Control-Max-Age: 180
|
||||
Vary: "*"
|
||||
X-XSS-Protection: 1; mode=block
|
||||
Content-Security-Policy: "object-src 'self'; frame-ancestors 'self';"
|
||||
X-Content-Type-Options: nosniff
|
||||
@@ -156,11 +146,6 @@ providers:
|
||||
# secret: aaaa-bbbb-cccc-dddd
|
||||
# no_tls_verify: true
|
||||
|
||||
# To relay the downstream client address to a TCP upstream, set
|
||||
# `relay_proxy_protocol_header: true` on that specific TCP route in route
|
||||
# configuration (for example, see providers.example.yml). UDP relay is not
|
||||
# supported yet.
|
||||
|
||||
# Match domains
|
||||
# See https://docs.godoxy.dev/Certificates-and-domain-matching
|
||||
#
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
services:
|
||||
netbird-dashboard:
|
||||
image: netbirdio/dashboard:latest
|
||||
container_name: netbird-dashboard
|
||||
restart: unless-stopped
|
||||
networks: [netbird-net]
|
||||
env_file: dashboard.env
|
||||
labels:
|
||||
proxy.aliases: netbird
|
||||
proxy.#1.port: 80
|
||||
proxy.#1.scheme: http
|
||||
proxy.#1.homepage.name: NetBird
|
||||
proxy.#1.homepage.icon: "@selfhst/netbird.svg"
|
||||
proxy.#1.homepage.category: networking
|
||||
proxy.#1.rules: | # https://docs.netbird.io/selfhosted/configuration-files
|
||||
path glob(/signalexchange.SignalExchange/**) | path glob(/management.ManagementService/**) | path glob(/management.ProxyService/**) {
|
||||
route netbird-grpc
|
||||
}
|
||||
path glob(/relay*) | path glob(/ws-proxy/**) | path glob(/api*) | path glob(/oauth2*) {
|
||||
route netbird-api
|
||||
}
|
||||
default {
|
||||
pass
|
||||
}
|
||||
netbird-server:
|
||||
image: netbirdio/netbird-server:latest
|
||||
container_name: netbird-server
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- netbird-net
|
||||
volumes:
|
||||
- netbird_data:/var/lib/netbird
|
||||
- ./config.yaml:/etc/netbird/config.yaml
|
||||
command: ["--config", "/etc/netbird/config.yaml"]
|
||||
labels:
|
||||
proxy.aliases: netbird-api, netbird-grpc
|
||||
proxy.*.port: 80
|
||||
proxy.*.homepage.show: false
|
||||
proxy.#1.scheme: http
|
||||
proxy.#2.scheme: h2c
|
||||
|
||||
networks:
|
||||
netbird-net:
|
||||
|
||||
volumes:
|
||||
netbird_data:
|
||||
174
go.mod
174
go.mod
@@ -1,6 +1,11 @@
|
||||
module github.com/yusing/godoxy
|
||||
|
||||
go 1.26.2
|
||||
go 1.26.0
|
||||
|
||||
exclude (
|
||||
github.com/moby/moby/api v1.53.0 // allow older daemon versions
|
||||
github.com/moby/moby/client v0.2.2 // allow older daemon versions
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/coreos/go-oidc/v3 => ./internal/go-oidc
|
||||
@@ -15,73 +20,78 @@ replace (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.12.0 // parsing HTML for extract fav icon; modify_html middleware
|
||||
github.com/PuerkitoBio/goquery v1.11.0 // parsing HTML for extract fav icon; modify_html middleware
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // backoff for retrying operations
|
||||
github.com/coreos/go-oidc/v3 v3.18.0 // oidc authentication
|
||||
github.com/coreos/go-oidc/v3 v3.17.0 // oidc authentication
|
||||
github.com/fsnotify/fsnotify v1.9.0 // file watcher
|
||||
github.com/gin-gonic/gin v1.12.0 // api server
|
||||
github.com/go-acme/lego/v4 v4.35.1 // acme client
|
||||
github.com/go-playground/validator/v10 v10.30.2 // validator
|
||||
github.com/gin-gonic/gin v1.11.0 // api server
|
||||
github.com/go-acme/lego/v4 v4.32.0 // acme client
|
||||
github.com/go-playground/validator/v10 v10.30.1 // validator
|
||||
github.com/gobwas/glob v0.2.3 // glob matcher for route rules
|
||||
github.com/gorilla/websocket v1.5.3 // websocket for API and agent
|
||||
github.com/gotify/server/v2 v2.9.1 // reference the Message struct for json response
|
||||
github.com/gotify/server/v2 v2.9.0 // reference the Message struct for json response
|
||||
github.com/lithammer/fuzzysearch v1.1.8 // fuzzy search for searching icons and filtering metrics
|
||||
github.com/pires/go-proxyproto v0.12.0 // proxy protocol support
|
||||
github.com/puzpuzpuz/xsync/v4 v4.5.0 // lock free map for concurrent operations
|
||||
github.com/rs/zerolog v1.35.1 // logging
|
||||
github.com/pires/go-proxyproto v0.11.0 // proxy protocol support
|
||||
github.com/puzpuzpuz/xsync/v4 v4.4.0 // lock free map for concurrent operations
|
||||
github.com/rs/zerolog v1.34.0 // logging
|
||||
github.com/vincent-petithory/dataurl v1.0.0 // data url for fav icon
|
||||
golang.org/x/crypto v0.50.0 // encrypting password with bcrypt
|
||||
golang.org/x/net v0.53.0 // HTTP header utilities
|
||||
golang.org/x/oauth2 v0.36.0 // oauth2 authentication
|
||||
golang.org/x/sync v0.20.0 // errgroup and singleflight for concurrent operations
|
||||
golang.org/x/time v0.15.0 // time utilities
|
||||
golang.org/x/crypto v0.48.0 // encrypting password with bcrypt
|
||||
golang.org/x/net v0.50.0 // HTTP header utilities
|
||||
golang.org/x/oauth2 v0.35.0 // oauth2 authentication
|
||||
golang.org/x/sync v0.19.0 // errgroup and singleflight for concurrent operations
|
||||
golang.org/x/time v0.14.0 // time utilities
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/gopkg v0.1.4 // xxhash64 for fast hash
|
||||
github.com/bytedance/sonic v1.15.0 // indirect; fast json parsing
|
||||
github.com/bytedance/gopkg v0.1.3 // xxhash64 for fast hash
|
||||
github.com/bytedance/sonic v1.15.0 // fast json parsing
|
||||
github.com/docker/cli v29.2.1+incompatible // needs docker/cli/cli/connhelper connection helper for docker client
|
||||
github.com/goccy/go-yaml v1.19.2 // yaml parsing for different config files
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 // jwt authentication
|
||||
github.com/luthermonson/go-proxmox v0.4.1 // proxmox API client
|
||||
github.com/luthermonson/go-proxmox v0.4.0 // proxmox API client
|
||||
github.com/moby/moby/api v1.52.0 // docker API
|
||||
github.com/moby/moby/client v0.2.1 // docker client
|
||||
github.com/oschwald/maxminddb-golang v1.13.1 // maxminddb for geoip database
|
||||
github.com/quic-go/quic-go v0.59.0 // http3 support
|
||||
github.com/shirou/gopsutil/v4 v4.26.3 // system information
|
||||
github.com/shirou/gopsutil/v4 v4.26.1 // system information
|
||||
github.com/spf13/afero v1.15.0 // afero for file system operations
|
||||
github.com/stretchr/testify v1.11.1 // testing framework
|
||||
github.com/valyala/fasthttp v1.70.0 // fast http for health check
|
||||
github.com/valyala/fasthttp v1.69.0 // fast http for health check
|
||||
github.com/yusing/ds v0.4.1 // data structures and algorithms
|
||||
github.com/yusing/godoxy/agent v0.0.0-20260423092340-5699f004c807
|
||||
github.com/yusing/godoxy/internal/dnsproviders v0.0.0-20260423092340-5699f004c807
|
||||
github.com/yusing/godoxy/agent v0.0.0-20260218101334-add7884a365e
|
||||
github.com/yusing/godoxy/internal/dnsproviders v0.0.0-20260218101334-add7884a365e
|
||||
github.com/yusing/gointernals v0.2.0
|
||||
github.com/yusing/goutils v0.7.0
|
||||
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260423020826-a0d979d1386c
|
||||
github.com/yusing/goutils/http/websocket v0.0.0-20260423020826-a0d979d1386c
|
||||
github.com/yusing/goutils/server v0.0.0-20260423020826-a0d979d1386c
|
||||
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260218062549-0b0fa3a059ec
|
||||
github.com/yusing/goutils/http/websocket v0.0.0-20260218062549-0b0fa3a059ec
|
||||
github.com/yusing/goutils/server v0.0.0-20260218062549-0b0fa3a059ec
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/auth v0.20.0 // indirect
|
||||
cloud.google.com/go/auth v0.18.2 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.1 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||
github.com/buger/goterm v1.0.4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/diskfs/go-diskfs v1.9.1 // indirect
|
||||
github.com/diskfs/go-diskfs v1.7.0 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/djherbis/times v1.6.0 // indirect
|
||||
github.com/ebitengine/purego v0.10.0 // indirect
|
||||
github.com/docker/go-connections v0.6.0
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/ebitengine/purego v0.9.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
@@ -89,51 +99,52 @@ require (
|
||||
github.com/gofrs/flock v0.13.0 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.22.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.12 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||
github.com/jinzhu/copier v0.4.0 // indirect
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/magefile/mage v1.17.2 // indirect
|
||||
github.com/magefile/mage v1.15.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.21 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/miekg/dns v1.1.72 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/nrdcg/goacmedns v0.2.0 // indirect
|
||||
github.com/nrdcg/porkbun v0.4.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/ovh/go-ovh v1.9.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.3.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/samber/lo v1.53.0 // indirect
|
||||
github.com/samber/slog-common v0.22.0 // indirect
|
||||
github.com/samber/slog-zerolog/v2 v2.9.2 // indirect
|
||||
github.com/samber/lo v1.52.0 // indirect
|
||||
github.com/samber/slog-common v0.20.0 // indirect
|
||||
github.com/samber/slog-zerolog/v2 v2.9.1 // indirect
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/sony/gobreaker v1.0.0 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0
|
||||
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||
go.uber.org/atomic v1.11.0
|
||||
go.uber.org/ratelimit v0.3.1 // indirect
|
||||
golang.org/x/mod v0.35.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
golang.org/x/tools v0.44.0 // indirect
|
||||
google.golang.org/api v0.276.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 // indirect
|
||||
google.golang.org/grpc v1.80.0 // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
google.golang.org/api v0.267.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
|
||||
google.golang.org/grpc v1.79.1 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/ini.v1 v1.67.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
@@ -141,48 +152,31 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v13 v13.1.0 // indirect
|
||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e // indirect
|
||||
github.com/andybalholm/brotli v1.2.1 // indirect
|
||||
github.com/bodgit/tsig v1.2.2 // indirect
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/boombuler/barcode v1.1.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.1 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/docker/cli v29.4.1+incompatible
|
||||
github.com/docker/go-connections v0.7.0
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/gin-contrib/sse v1.1.1 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
|
||||
github.com/go-resty/resty/v2 v2.17.2 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/goccy/go-json v0.10.6 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/google/go-querystring v1.2.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
|
||||
github.com/jcmturner/gofork v1.7.6 // indirect
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
|
||||
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
|
||||
github.com/klauspost/compress v1.18.5 // indirect
|
||||
github.com/klauspost/compress v1.18.4 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||
github.com/linode/linodego v1.68.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1
|
||||
github.com/moby/moby/api v1.52.0
|
||||
github.com/moby/moby/client v0.2.1
|
||||
github.com/linode/linodego v1.65.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect
|
||||
github.com/nrdcg/goinwx v0.12.0 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.113.0 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.113.0 // indirect
|
||||
github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b // indirect
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/pion/dtls/v3 v3.1.2 // indirect
|
||||
github.com/pion/logging v0.2.4 // indirect
|
||||
@@ -194,23 +188,9 @@ require (
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/vultr/govultr/v3 v3.31.1 // indirect
|
||||
github.com/vultr/govultr/v3 v3.27.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.1 // indirect
|
||||
golang.org/x/arch v0.26.0 // indirect
|
||||
)
|
||||
|
||||
exclude (
|
||||
github.com/moby/moby/api v1.53.0
|
||||
github.com/moby/moby/api v1.54.0
|
||||
github.com/moby/moby/api v1.54.1
|
||||
github.com/moby/moby/api v1.54.2
|
||||
)
|
||||
|
||||
exclude (
|
||||
github.com/moby/moby/client v0.2.2
|
||||
github.com/moby/moby/client v0.3.0
|
||||
github.com/moby/moby/client v0.4.0
|
||||
github.com/moby/moby/client v0.4.1
|
||||
golang.org/x/arch v0.24.0 // indirect
|
||||
)
|
||||
|
||||
325
go.sum
325
go.sum
@@ -1,18 +1,18 @@
|
||||
cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA=
|
||||
cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q=
|
||||
cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
|
||||
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 h1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1/go.mod h1:pzBXCYn05zvYIrwLgtK8Ap8QcjRg+0i76tMQdWN6wOk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw=
|
||||
@@ -25,21 +25,18 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.1 h1:edShSHV3DV90+kt+CMaEXEzR9QF7wFrPJxVGz2blMIU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.1/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/PuerkitoBio/goquery v1.12.0 h1:pAcL4g3WRXekcB9AU/y1mbKez2dbY2AajVhtkO8RIBo=
|
||||
github.com/PuerkitoBio/goquery v1.12.0/go.mod h1:802ej+gV2y7bbIhOIoPY5sT183ZW0YFofScC4q/hIpQ=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v13 v13.1.0 h1:KvfpO2utLmpRq0fbC0UZRzdCERfLGLX1/dcYvG7pP7k=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v13 v13.1.0/go.mod h1:AxGyKKxAxaCNeGadscLgo+gBYEAKhNG6tRR5O0HjV30=
|
||||
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
|
||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
|
||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
|
||||
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 h1:h/33OxYLqBk0BYmEbSUy7MlvgQR/m1w1/7OJFKoPL1I=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0/go.mod h1:rvh3imDA6EaQi+oM/GQHkQAOHbXPKJ7EWJvfjuw141Q=
|
||||
github.com/anchore/go-lzo v0.1.0 h1:NgAacnzqPeGH49Ky19QKLBZEuFRqtTG9cdaucc3Vncs=
|
||||
github.com/anchore/go-lzo v0.1.0/go.mod h1:3kLx0bve2oN1iDwgM1U5zGku1Tfbdb0No5qp1eL1fIk=
|
||||
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
|
||||
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||
@@ -47,19 +44,17 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
|
||||
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/bodgit/tsig v1.2.2 h1:RgxTCr8UFUHyU4D8Ygb2UtXtS4niw4B6XYYBpgCjl0k=
|
||||
github.com/bodgit/tsig v1.2.2/go.mod h1:rIGNOLZOV/UA03fmCUtEFbpWOrIoaOuETkpaeTvnLF4=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
|
||||
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY=
|
||||
github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE=
|
||||
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
|
||||
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||
github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI=
|
||||
github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
@@ -70,27 +65,27 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG
|
||||
github.com/containerd/errdefs v1.0.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/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/diskfs/go-diskfs v1.9.1 h1:g/UCTC5jZFomhtH4DyF9fG1eRHGgDIjSd1hSjEErXn0=
|
||||
github.com/diskfs/go-diskfs v1.9.1/go.mod h1:rW9+4MPN1tbMpQqRZlcM3YQsh3Ucc+Q1k1iIqzzmZcg=
|
||||
github.com/diskfs/go-diskfs v1.7.0 h1:vonWmt5CMowXwUc79jWyGrf2DIMeoOjkLlMnQYGVOs8=
|
||||
github.com/diskfs/go-diskfs v1.7.0/go.mod h1:LhQyXqOugWFRahYUSw47NyZJPezFzB9UELwhpszLP/k=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||
github.com/docker/cli v29.4.1+incompatible h1:02RT8QqqwtGRn+6SYypv8IUEbD/ltY6sfKCJIoUcGzk=
|
||||
github.com/docker/cli v29.4.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=
|
||||
github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
|
||||
github.com/docker/cli v29.2.1+incompatible h1:n3Jt0QVCN65eiVBoUTZQM9mcQICCJt3akW4pKAbKdJg=
|
||||
github.com/docker/cli v29.2.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
||||
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab h1:h1UgjJdAAhj+uPL68n7XASS6bU+07ZX1WJvVS2eyoeY=
|
||||
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab/go.mod h1:GLo/8fDswSAniFG+BFIaiSPcK610jyzgEhWYPQwuQdw=
|
||||
github.com/enceve/crypto v0.0.0-20160707101852-34d48bb93815/go.mod h1:wYFFK4LYXbX7j+76mOq7aiC/EAw2S22CrzPHqgsisPw=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
@@ -101,16 +96,15 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
|
||||
github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
|
||||
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
||||
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
||||
github.com/go-acme/lego/v4 v4.35.1 h1:xTcEBsENs4Ek5EiNpN3JXy7W5aynf5KhJ6D/HuuhXv4=
|
||||
github.com/go-acme/lego/v4 v4.35.1/go.mod h1:E+4l5mPPg9dwhTz6JJptNLvtj94E2JDObI50QuFJd3s=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||
github.com/go-acme/lego/v4 v4.32.0 h1:z7Ss7aa1noabhKj+DBzhNCO2SM96xhE3b0ucVW3x8Tc=
|
||||
github.com/go-acme/lego/v4 v4.32.0/go.mod h1:lI2fZNdgeM/ymf9xQ9YKbgZm6MeDuf91UrohMQE4DhI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
@@ -126,8 +120,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ=
|
||||
github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc=
|
||||
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||
github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk=
|
||||
github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
@@ -136,10 +130,11 @@ github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPE
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
||||
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
|
||||
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
@@ -156,60 +151,34 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.15 h1:xolVQTEXusUcAA5UgtyRLjelpFFHWlPQ4XfWGc7MBas=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.15/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
||||
github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4=
|
||||
github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.12 h1:Fg+zsqzYEs1ZnvmcztTYxhgCBsx3eEhEwQ1W/lHq/sQ=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.12/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
|
||||
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gotify/server/v2 v2.9.1 h1:wsQUCdYJ4ZvP7RIRKDLtAtmFQc3kxbrv3QqccO5RWzs=
|
||||
github.com/gotify/server/v2 v2.9.1/go.mod h1:8scw0hiExomp4rJDrXBwRIcgQm7kv74P4Z4B+iM4l8w=
|
||||
github.com/gotify/server/v2 v2.9.0 h1:2zRCl28wkq0oc6YNbyJS2n0dDOOVvOS3Oez5AG2ij54=
|
||||
github.com/gotify/server/v2 v2.9.0/go.mod h1:249wwlUqHTr0QsiKARGtFVqds0pNLIMjYLinHyMACdQ=
|
||||
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
|
||||
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A=
|
||||
github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
|
||||
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
|
||||
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.3/go.mod h1:dqRwJGXznQrzw6cWmyo6kH+E7jksEQG/CyVWsJEsJO0=
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU=
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl5BIHNXfS9+C35ZyJaklL7mLDbgUkcgXzSLa8Tk0=
|
||||
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
|
||||
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=
|
||||
@@ -222,21 +191,23 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/linode/linodego v1.68.0 h1:lAsXuHm/cwQT3KCbVpMGtRiH8IpQl4hUuBOXpqkuNwo=
|
||||
github.com/linode/linodego v1.68.0/go.mod h1:X7nmTNq1GmZT4bG6w9aiuVrOnhVxYaywrzxM+buC/qU=
|
||||
github.com/linode/linodego v1.65.0 h1:SdsuGD8VSsPWeShXpE7ihl5vec+fD3MgwhnfYC/rj7k=
|
||||
github.com/linode/linodego v1.65.0/go.mod h1:tOFiTErdjkbVnV+4S0+NmIE9dqqZUEM2HsJaGu8wMh8=
|
||||
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
||||
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
||||
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e h1:Q6MvJtQK/iRcRtzAscm/zF23XxJlbECiGPyRicsX+Ak=
|
||||
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||
github.com/magefile/mage v1.17.2 h1:fyXVu1eadI8Ap1HCCNgEhJ5McIWiYhLR8uol64ZZc40=
|
||||
github.com/magefile/mage v1.17.2/go.mod h1:Yj51kqllmsgFpvvSzgrZPK9WtluG3kUhFaBUVLo4feA=
|
||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM=
|
||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
|
||||
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/maxatome/go-testdeep v1.14.0 h1:rRlLv1+kI8eOI3OaBXZwb3O7xY3exRzdW5QyX48g9wI=
|
||||
github.com/maxatome/go-testdeep v1.14.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
@@ -250,31 +221,28 @@ github.com/moby/moby/client v0.2.1/go.mod h1:O+/tw5d4a1Ha/ZA/tPxIZJapJRUS6LNZ1wi
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0=
|
||||
github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg=
|
||||
github.com/nrdcg/goinwx v0.12.0 h1:ujdUqDBnaRSFwzVnImvPHYw3w3m9XgmGImNUw1GyMb4=
|
||||
github.com/nrdcg/goinwx v0.12.0/go.mod h1:IrVKd3ZDbFiMjdPgML4CSxZAY9wOoqLvH44zv3NodJ0=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.113.0 h1:OLlJVGHkTHBCXdTGpNn5ay4DV3gOZrVLxlUM6xBQrIM=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.113.0/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.113.0 h1:bveOZN6gZZjjEM1T9o1TUm8de3zePyDbfuluMpaRJCE=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.113.0/go.mod h1:Ff6Cxm43tuGJHyltQwD3EXLs7kKpkxzqy6DGQcaiS/0=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2 h1:OWijzl3nHUApvTivl+3+78dbBwmyEHOnb+W9m6ixGbk=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2 h1:9LsjN/zaIN7H8JE61NHpbWhxF0UGY96+kMlk3g8OvGU=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2/go.mod h1:32vZH06TuwZSn+IDMO1qcDvC2vHVlzUALCwXGWPA+dc=
|
||||
github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw=
|
||||
github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b h1:it0YPE/evO6/m8t8wxis9KFI2F/aleOKsI6d9uz0cEk=
|
||||
github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b/go.mod h1:tNrEB5k8SI+g5kOlsCmL2ELASfpqEofI0+FLBgBdN08=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
||||
github.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE=
|
||||
github.com/ovh/go-ovh v1.9.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
|
||||
github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM=
|
||||
github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc=
|
||||
@@ -283,12 +251,13 @@ github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
|
||||
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
|
||||
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
|
||||
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
|
||||
github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=
|
||||
github.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI=
|
||||
github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
|
||||
github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/xattr v0.4.12 h1:rRTkSyFNTRElv6pkA3zpjHpQ90p/OdHQC1GmGh1aTjM=
|
||||
github.com/pkg/xattr v0.4.12/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=
|
||||
github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@@ -296,22 +265,23 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.5.0 h1:vOSWu6b57/emh+L/Cw0BeQfvxa/cogFywXHeGUxQxAg=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.5.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.4.0 h1:vlSN6/CkEY0pY8KaB0yqo/pCLZvp9nhdbBdjipT4gWo=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.4.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI=
|
||||
github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
||||
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
|
||||
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/samber/slog-common v0.22.0 h1:WyPxYRg/c5xUmxZJbtd0QgysHlLBhRA+MngKdJieHxE=
|
||||
github.com/samber/slog-common v0.22.0/go.mod h1:d/6OaSlzdkl9PFpfRLgn8FwY1OW6EFmPtBpsHX4MrU0=
|
||||
github.com/samber/slog-zerolog/v2 v2.9.2 h1:DIFzfzDTxHeRyGlfg/D7b2by7VVzcsBTybRPrzjWF4c=
|
||||
github.com/samber/slog-zerolog/v2 v2.9.2/go.mod h1:2q6cYK2OcN6YfQE/WyCnUtigc+yYf3ozqGsGmRwZR6I=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/samber/slog-common v0.20.0 h1:WaLnm/aCvBJSk5nR5aXZTFBaV0B47A+AEaEOiZDeUnc=
|
||||
github.com/samber/slog-common v0.20.0/go.mod h1:+Ozat1jgnnE59UAlmNX1IF3IByHsODnnwf9jUcBZ+m8=
|
||||
github.com/samber/slog-zerolog/v2 v2.9.1 h1:RMOq8XqzfuGx1X0TEIlS9OXbbFmqLY2/wJppghz66YY=
|
||||
github.com/samber/slog-zerolog/v2 v2.9.1/go.mod h1:DQYYve14WgCRN/XnKeHl4266jXK0DgYkYXkfZ4Fp98k=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 h1:ObX9hZmK+VmijreZO/8x9pQ8/P/ToHD/bdSb4Eg4tUo=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36/go.mod h1:LEsDu4BubxK7/cWhtlQWfuxwL4rf/2UEpxXz1o1EMtM=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
@@ -330,7 +300,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
@@ -347,17 +316,16 @@ github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.70.0 h1:LAhMGcWk13QZWm85+eg8ZBNbrq5mnkWFGbHMUJHIdXA=
|
||||
github.com/valyala/fasthttp v1.70.0/go.mod h1:oDZEHHkJ/Buyklg6uURmYs19442zFSnCIfX3j1FY3pE=
|
||||
github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI=
|
||||
github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw=
|
||||
github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI=
|
||||
github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U=
|
||||
github.com/vultr/govultr/v3 v3.31.1 h1:AoJRZ0WDS1J1otp2wk3OD4aYh4EQxFU+kvyOPdCe4+Y=
|
||||
github.com/vultr/govultr/v3 v3.31.1/go.mod h1:2zyUw9yADQaGwKnwDesmIOlBNLrm7edsCfWHFJpWKf8=
|
||||
github.com/vultr/govultr/v3 v3.27.0 h1:J8etMyu/Jh5+idMsu2YZpOWmDXXHeW4VZnkYXmJYHx8=
|
||||
github.com/vultr/govultr/v3 v3.27.0/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusing/ds v0.4.1 h1:syMCh7hO6Yw8xfcFkEaln3W+lVeWB/U/meYv6Wf2/Ig=
|
||||
github.com/yusing/ds v0.4.1/go.mod h1:XhKV4l7cZwBbbl7lRzNC9zX27zvCM0frIwiuD40ULRk=
|
||||
@@ -365,104 +333,87 @@ github.com/yusing/gointernals v0.2.0 h1:jyWB3kdUPkuU6s0r8QY/sS5h2WNBF4Kfisly8dtS
|
||||
github.com/yusing/gointernals v0.2.0/go.mod h1:xGzNbPGMm5Z8kG0t4JYISMscw+gMQlgghkLxlgRZv5Y=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.1 h1:j2U/Qp+wvueSpqitLCSZPT/+ZpVc1xzuwdHWwl7d8ro=
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.1/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
|
||||
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
|
||||
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||
go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0=
|
||||
go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk=
|
||||
golang.org/x/arch v0.26.0 h1:jZ6dpec5haP/fUv1kLCbuJy6dnRrfX6iVK08lZBFpk4=
|
||||
golang.org/x/arch v0.26.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
||||
golang.org/x/arch v0.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y=
|
||||
golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
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.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
||||
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -474,7 +425,6 @@ golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
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=
|
||||
@@ -482,34 +432,31 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
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.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/api v0.276.0 h1:nVArUtfLEihtW+b0DdcqRGK1xoEm2+ltAihyztq7MKY=
|
||||
google.golang.org/api v0.276.0/go.mod h1:Fnag/EWUPIcJXuIkP1pjoTgS5vdxlk3eeemL7Do6bvw=
|
||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
|
||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 h1:XF8+t6QQiS0o9ArVan/HW8Q7cycNPGsJf6GA2nXxYAg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/api v0.267.0 h1:w+vfWPMPYeRs8qH1aYYsFX68jMls5acWl/jocfLomwE=
|
||||
google.golang.org/api v0.267.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0=
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
2
goutils
2
goutils
Submodule goutils updated: 586f5c382e...3be815cb6e
@@ -69,7 +69,7 @@ Initializes the ACL, starts the logger and notification goroutines.
|
||||
func (c *Config) IPAllowed(ip net.IP) bool
|
||||
```
|
||||
|
||||
Returns true if the IP is allowed based on configured rules. Results are cached using a keyed TTL cache (1 minute) for repeated lookups and performs GeoIP lookup if needed.
|
||||
Returns true if the IP is allowed based on configured rules. Performs caching and GeoIP lookup if needed.
|
||||
|
||||
```go
|
||||
func (c *Config) WrapTCP(lis net.Listener) net.Listener
|
||||
@@ -216,8 +216,7 @@ No metrics are currently exposed.
|
||||
## Security Considerations
|
||||
|
||||
- Loopback and private IPs are always allowed unless explicitly denied
|
||||
- ACL decisions are cached for 1 minute (TTL) to balance performance and memory usage
|
||||
- Cache uses least-recently-used (LRU) eviction [or document actual eviction policy]
|
||||
- Cache TTL is 1 minute to limit memory usage
|
||||
- Notification channel has a buffer of 100 to prevent blocking
|
||||
- Failed connections are immediately closed without response
|
||||
|
||||
@@ -228,6 +227,7 @@ No metrics are currently exposed.
|
||||
| Invalid matcher syntax | Validation fails on startup | Fix configuration syntax |
|
||||
| MaxMind database unavailable | GeoIP lookups return unknown location | Default action applies; cache hit still works |
|
||||
| Notification provider unavailable | Notification dropped | Error logged, continues operation |
|
||||
| Cache full | No eviction, uses Go map | No action needed |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
package acl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
"github.com/yusing/godoxy/internal/logging/accesslog"
|
||||
"github.com/yusing/godoxy/internal/maxmind"
|
||||
"github.com/yusing/godoxy/internal/notif"
|
||||
"github.com/yusing/goutils/cache"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
aclevents "github.com/yusing/goutils/events/acl"
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
@@ -21,8 +20,6 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
config
|
||||
|
||||
Default string `json:"default" validate:"omitempty,oneof=allow deny"` // default: allow
|
||||
AllowLocal *bool `json:"allow_local"` // default: true
|
||||
Allow Matchers `json:"allow"`
|
||||
@@ -35,6 +32,7 @@ type Config struct {
|
||||
IncludeAllowed *bool `json:"include_allowed,omitzero"` // default: false
|
||||
} `json:"notify"`
|
||||
|
||||
config
|
||||
valErr gperr.Error
|
||||
}
|
||||
|
||||
@@ -43,7 +41,7 @@ const defaultNotifyInterval = 1 * time.Minute
|
||||
type config struct {
|
||||
defaultAllow bool
|
||||
allowLocal bool
|
||||
ipCache cache.CachedContextKeyFunc[*checkCache, string]
|
||||
ipCache *xsync.Map[string, *checkCache]
|
||||
|
||||
// will be nil if Notify.To is empty
|
||||
// these are per IP, reset every Notify.Interval
|
||||
@@ -68,9 +66,9 @@ type config struct {
|
||||
|
||||
type checkCache struct {
|
||||
*maxmind.IPInfo
|
||||
|
||||
allow bool
|
||||
reason string
|
||||
created time.Time
|
||||
}
|
||||
|
||||
type ipLog struct {
|
||||
@@ -81,6 +79,10 @@ type ipLog struct {
|
||||
|
||||
const cacheTTL = 1 * time.Minute
|
||||
|
||||
func (c *checkCache) Expired() bool {
|
||||
return c.created.Add(cacheTTL).Before(time.Now())
|
||||
}
|
||||
|
||||
// TODO: add stats
|
||||
|
||||
const (
|
||||
@@ -118,7 +120,7 @@ func (c *Config) Validate() error {
|
||||
return c.valErr
|
||||
}
|
||||
|
||||
c.ipCache = cache.NewKeyFunc(c.evaluateIP).WithTTL(cacheTTL).Build()
|
||||
c.ipCache = xsync.NewMap[string, *checkCache]()
|
||||
|
||||
if c.Notify.IncludeAllowed != nil {
|
||||
c.notifyAllowed = *c.Notify.IncludeAllowed
|
||||
@@ -169,36 +171,16 @@ func (c *Config) Start(parent task.Parent) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) newCheckCache(info *maxmind.IPInfo, allow bool, reason string) *checkCache {
|
||||
func (c *Config) cacheRecord(info *maxmind.IPInfo, allow bool, reason string) {
|
||||
if common.ForceResolveCountry && info.City == nil {
|
||||
maxmind.LookupCity(info)
|
||||
}
|
||||
return &checkCache{
|
||||
c.ipCache.Store(info.Str, &checkCache{
|
||||
IPInfo: info,
|
||||
allow: allow,
|
||||
reason: reason,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) evaluateIP(_ context.Context, ipStr string) (*checkCache, error) {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("invalid IP: %q", ipStr)
|
||||
}
|
||||
|
||||
ipInfo := &maxmind.IPInfo{IP: ip, Str: ipStr}
|
||||
if index := c.Deny.MatchedIndex(ipInfo); index != -1 {
|
||||
return c.newCheckCache(ipInfo, false, "blocked by deny rule: "+c.Deny[index].raw), nil
|
||||
}
|
||||
if index := c.Allow.MatchedIndex(ipInfo); index != -1 {
|
||||
return c.newCheckCache(ipInfo, true, "allowed by allow rule: "+c.Allow[index].raw), nil
|
||||
}
|
||||
|
||||
reason := "denied by default"
|
||||
if c.defaultAllow {
|
||||
reason = "allowed by default"
|
||||
}
|
||||
return c.newCheckCache(ipInfo, c.defaultAllow, reason), nil
|
||||
created: time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Config) needLogOrNotify() bool {
|
||||
@@ -214,16 +196,14 @@ func (c *Config) needNotify() bool {
|
||||
}
|
||||
|
||||
func (c *Config) getCachedCity(ip string) string {
|
||||
record, err := c.ipCache(context.Background(), ip)
|
||||
if err != nil {
|
||||
return "unknown location"
|
||||
record, ok := c.ipCache.Load(ip)
|
||||
if ok {
|
||||
if record.City != nil {
|
||||
if record.City.Country.IsoCode != "" {
|
||||
return record.City.Country.IsoCode
|
||||
}
|
||||
city := record.IPInfo.City
|
||||
if city != nil {
|
||||
if city.Country.IsoCode != "" {
|
||||
return city.Country.IsoCode
|
||||
return record.City.Location.TimeZone
|
||||
}
|
||||
return city.Location.TimeZone
|
||||
}
|
||||
return "unknown location"
|
||||
}
|
||||
@@ -308,11 +288,31 @@ func (c *Config) IPAllowed(ip net.IP) bool {
|
||||
}
|
||||
|
||||
ipStr := ip.String()
|
||||
record, err := c.ipCache(context.Background(), ipStr)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("ip", ipStr).Msg("unexpected ACL cache lookup error")
|
||||
record = c.newCheckCache(&maxmind.IPInfo{IP: ip, Str: ipStr}, c.defaultAllow, "invalid ACL cache lookup")
|
||||
}
|
||||
record, ok := c.ipCache.Load(ipStr)
|
||||
if ok && !record.Expired() {
|
||||
c.logAndNotify(record.IPInfo, record.allow, record.reason)
|
||||
return record.allow
|
||||
}
|
||||
|
||||
ipAndStr := &maxmind.IPInfo{IP: ip, Str: ipStr}
|
||||
if index := c.Deny.MatchedIndex(ipAndStr); index != -1 {
|
||||
reason := "blocked by deny rule: " + c.Deny[index].raw
|
||||
c.logAndNotify(ipAndStr, false, reason)
|
||||
c.cacheRecord(ipAndStr, false, reason)
|
||||
return false
|
||||
}
|
||||
if index := c.Allow.MatchedIndex(ipAndStr); index != -1 {
|
||||
reason := "allowed by allow rule: " + c.Allow[index].raw
|
||||
c.logAndNotify(ipAndStr, true, reason)
|
||||
c.cacheRecord(ipAndStr, true, reason)
|
||||
return true
|
||||
}
|
||||
|
||||
reason := "denied by default"
|
||||
if c.defaultAllow {
|
||||
reason = "allowed by default"
|
||||
}
|
||||
c.logAndNotify(ipAndStr, c.defaultAllow, reason)
|
||||
c.cacheRecord(ipAndStr, c.defaultAllow, reason)
|
||||
return c.defaultAllow
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
package acl
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIPAllowedCachesDecision(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testIP := net.ParseIP("8.8.8.8")
|
||||
require.NotNil(t, testIP)
|
||||
|
||||
t.Run("cached allow survives rule changes", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := &Config{
|
||||
Default: ACLDeny,
|
||||
AllowLocal: new(false),
|
||||
Allow: mustMatchers(t, "ip:8.8.8.8"),
|
||||
}
|
||||
require.NoError(t, cfg.Validate())
|
||||
|
||||
require.True(t, cfg.IPAllowed(testIP))
|
||||
|
||||
cfg.Allow = nil
|
||||
cfg.Deny = mustMatchers(t, "ip:8.8.8.8")
|
||||
|
||||
require.True(t, cfg.IPAllowed(testIP))
|
||||
})
|
||||
|
||||
t.Run("cached deny survives rule changes", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := &Config{
|
||||
Default: ACLAllow,
|
||||
AllowLocal: new(false),
|
||||
Deny: mustMatchers(t, "ip:8.8.8.8"),
|
||||
}
|
||||
require.NoError(t, cfg.Validate())
|
||||
|
||||
require.False(t, cfg.IPAllowed(testIP))
|
||||
|
||||
cfg.Deny = nil
|
||||
cfg.Allow = mustMatchers(t, "ip:8.8.8.8")
|
||||
|
||||
require.False(t, cfg.IPAllowed(testIP))
|
||||
})
|
||||
}
|
||||
|
||||
func mustMatchers(t *testing.T, rules ...string) Matchers {
|
||||
t.Helper()
|
||||
|
||||
matchers := make(Matchers, len(rules))
|
||||
for i, rule := range rules {
|
||||
require.NoError(t, matchers[i].Parse(rule))
|
||||
}
|
||||
return matchers
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"encoding/json"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/valyala/fasthttp"
|
||||
agentPkg "github.com/yusing/godoxy/agent/pkg/agent"
|
||||
@@ -63,7 +63,7 @@ func (agent *Agent) DoHealthCheck(timeout time.Duration, query string) (ret Heal
|
||||
ret.Detail = fmt.Sprintf("HTTP %d %s", status, resp.Body())
|
||||
return ret, nil
|
||||
} else {
|
||||
err = json.Unmarshal(resp.Body(), &ret)
|
||||
err = sonic.Unmarshal(resp.Body(), &ret)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yusing/godoxy/internal/auth"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
)
|
||||
|
||||
// CSRFMiddleware implements the Signed Double Submit Cookie pattern.
|
||||
//
|
||||
// Safe methods (GET/HEAD/OPTIONS): ensure a signed CSRF cookie exists.
|
||||
// Unsafe methods (POST/PUT/DELETE/PATCH): require X-CSRF-Token header
|
||||
// matching the cookie value, with a valid HMAC signature.
|
||||
func CSRFMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
switch c.Request.Method {
|
||||
case http.MethodGet, http.MethodHead, http.MethodOptions:
|
||||
ensureCSRFCookie(c)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
if allowSameOriginAuthBootstrap(c.Request) {
|
||||
ensureCSRFCookie(c)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
cookie, err := c.Request.Cookie(auth.CSRFCookieName)
|
||||
if err != nil {
|
||||
// No cookie at all — issue one so the frontend can retry.
|
||||
reissueCSRFCookie(c)
|
||||
c.JSON(http.StatusForbidden, apitypes.Error("missing CSRF token"))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
cookieToken := canonicalCSRFToken(cookie.Value)
|
||||
headerToken := canonicalCSRFToken(c.GetHeader(auth.CSRFHeaderName))
|
||||
if headerToken == "" || cookieToken != headerToken || !auth.ValidateCSRFToken(cookieToken) {
|
||||
// Stale or forged token — issue a fresh one so the
|
||||
// frontend can read the new cookie and retry.
|
||||
reissueCSRFCookie(c)
|
||||
c.JSON(http.StatusForbidden, apitypes.Error("invalid CSRF token"))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func ensureCSRFCookie(c *gin.Context) {
|
||||
if _, err := c.Request.Cookie(auth.CSRFCookieName); err == nil {
|
||||
return
|
||||
}
|
||||
reissueCSRFCookie(c)
|
||||
}
|
||||
|
||||
func reissueCSRFCookie(c *gin.Context) {
|
||||
token, err := auth.GenerateCSRFToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
auth.SetCSRFCookie(c.Writer, c.Request, token)
|
||||
}
|
||||
|
||||
func allowSameOriginAuthBootstrap(r *http.Request) bool {
|
||||
if r.Method != http.MethodPost {
|
||||
return false
|
||||
}
|
||||
switch r.URL.Path {
|
||||
case "/api/v1/auth/login", "/api/v1/auth/callback":
|
||||
return requestSourceMatchesHost(r)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func requestSourceMatchesHost(r *http.Request) bool {
|
||||
for _, header := range []string{"Origin", "Referer"} {
|
||||
value := r.Header.Get(header)
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
u, err := url.Parse(value)
|
||||
if err != nil || u.Host == "" {
|
||||
return false
|
||||
}
|
||||
return normalizeHost(u.Hostname()) == normalizeHost(r.Host)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func normalizeHost(host string) string {
|
||||
host = strings.ToLower(host)
|
||||
if h, _, err := net.SplitHostPort(host); err == nil {
|
||||
return h
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
func canonicalCSRFToken(token string) string {
|
||||
return strings.Trim(strings.TrimSpace(token), "\"")
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/yusing/godoxy/internal/auth"
|
||||
autocert "github.com/yusing/godoxy/internal/autocert/types"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
"github.com/yusing/goutils/task"
|
||||
)
|
||||
|
||||
func TestAuthCheckIssuesCSRFCookie(t *testing.T) {
|
||||
handler := newAuthenticatedHandler(t)
|
||||
|
||||
req := httptest.NewRequest(http.MethodHead, "/api/v1/auth/check", nil)
|
||||
req.Host = "app.example.com"
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusFound, rec.Code)
|
||||
|
||||
csrfCookie := findCookie(rec.Result().Cookies(), auth.CSRFCookieName)
|
||||
require.NotNil(t, csrfCookie)
|
||||
assert.NotEmpty(t, csrfCookie.Value)
|
||||
assert.Empty(t, csrfCookie.Domain)
|
||||
assert.Equal(t, "/", csrfCookie.Path)
|
||||
assert.Equal(t, http.SameSiteStrictMode, csrfCookie.SameSite)
|
||||
}
|
||||
|
||||
func TestUserPassCallbackAllowsSameOriginFormPostWithoutCSRFCookie(t *testing.T) {
|
||||
handler := newAuthenticatedHandler(t)
|
||||
|
||||
req := newJSONRequest(t, http.MethodPost, "/api/v1/auth/callback", map[string]string{
|
||||
"username": common.APIUser,
|
||||
"password": common.APIPassword,
|
||||
})
|
||||
req.Host = "app.example.com"
|
||||
req.Header.Set("Origin", "https://app.example.com")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
tokenCookie := findCookie(rec.Result().Cookies(), "godoxy_token")
|
||||
require.NotNil(t, tokenCookie)
|
||||
assert.NotEmpty(t, tokenCookie.Value)
|
||||
csrfCookie := findCookie(rec.Result().Cookies(), auth.CSRFCookieName)
|
||||
require.NotNil(t, csrfCookie)
|
||||
assert.NotEmpty(t, csrfCookie.Value)
|
||||
}
|
||||
|
||||
func TestUserPassCallbackRejectsCrossOriginPostWithoutCSRFCookie(t *testing.T) {
|
||||
handler := newAuthenticatedHandler(t)
|
||||
|
||||
req := newJSONRequest(t, http.MethodPost, "/api/v1/auth/callback", map[string]string{
|
||||
"username": common.APIUser,
|
||||
"password": common.APIPassword,
|
||||
})
|
||||
req.Host = "app.example.com"
|
||||
req.Header.Set("Origin", "https://evil.example.com")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusForbidden, rec.Code)
|
||||
csrfCookie := findCookie(rec.Result().Cookies(), auth.CSRFCookieName)
|
||||
require.NotNil(t, csrfCookie)
|
||||
assert.NotEmpty(t, csrfCookie.Value)
|
||||
}
|
||||
|
||||
func TestUserPassCallbackAcceptsValidCSRFCookie(t *testing.T) {
|
||||
handler := newAuthenticatedHandler(t)
|
||||
csrfCookie := issueCSRFCookie(t, handler)
|
||||
|
||||
req := newJSONRequest(t, http.MethodPost, "/api/v1/auth/callback", map[string]string{
|
||||
"username": common.APIUser,
|
||||
"password": common.APIPassword,
|
||||
})
|
||||
req.Host = "app.example.com"
|
||||
req.AddCookie(csrfCookie)
|
||||
req.Header.Set(auth.CSRFHeaderName, csrfCookie.Value)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
tokenCookie := findCookie(rec.Result().Cookies(), "godoxy_token")
|
||||
require.NotNil(t, tokenCookie)
|
||||
assert.NotEmpty(t, tokenCookie.Value)
|
||||
}
|
||||
|
||||
func TestUnsafeRequestAcceptsQuotedCSRFCookieValue(t *testing.T) {
|
||||
handler := newAuthenticatedHandler(t)
|
||||
csrfCookie := issueCSRFCookie(t, handler)
|
||||
sessionToken := issueSessionToken(t)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/logout", nil)
|
||||
req.Host = "app.example.com"
|
||||
req.Header.Set("Cookie", `godoxy_token=`+sessionToken+`; godoxy_csrf="`+csrfCookie.Value+`"`)
|
||||
req.Header.Set(auth.CSRFHeaderName, csrfCookie.Value)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusFound, rec.Code)
|
||||
}
|
||||
|
||||
func TestLogoutRequiresCSRFCookie(t *testing.T) {
|
||||
handler := newAuthenticatedHandler(t)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/logout", nil)
|
||||
req.Host = "app.example.com"
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusForbidden, rec.Code)
|
||||
}
|
||||
|
||||
func TestLoginAllowsSameOriginPostWithoutCSRFCookie(t *testing.T) {
|
||||
handler := newAuthenticatedHandler(t)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", nil)
|
||||
req.Host = "app.example.com"
|
||||
req.Header.Set("Origin", "https://app.example.com")
|
||||
req.Header.Set("Accept", "text/html")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusFound, rec.Code)
|
||||
csrfCookie := findCookie(rec.Result().Cookies(), auth.CSRFCookieName)
|
||||
require.NotNil(t, csrfCookie)
|
||||
assert.NotEmpty(t, csrfCookie.Value)
|
||||
}
|
||||
|
||||
func TestGetLogoutRouteStillAvailableForFrontend(t *testing.T) {
|
||||
handler := newAuthenticatedHandler(t)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/logout", nil)
|
||||
req.Host = "app.example.com"
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusFound, rec.Code)
|
||||
}
|
||||
|
||||
func TestCertRenewRejectsCrossOriginWebSocketRequest(t *testing.T) {
|
||||
handler := newAuthenticatedHandler(t)
|
||||
provider := &stubAutocertProvider{}
|
||||
sessionToken := issueSessionToken(t)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/cert/renew", nil)
|
||||
req.Host = "app.example.com"
|
||||
req.Header.Set("Connection", "Upgrade")
|
||||
req.Header.Set("Upgrade", "websocket")
|
||||
req.Header.Set("Sec-WebSocket-Version", "13")
|
||||
req.Header.Set("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==")
|
||||
req.Header.Set("Origin", "https://evil.example.com")
|
||||
req.AddCookie(&http.Cookie{Name: "godoxy_token", Value: sessionToken})
|
||||
req = req.WithContext(context.WithValue(req.Context(), autocert.ContextKey{}, provider))
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusForbidden, rec.Code)
|
||||
assert.Zero(t, provider.forceExpiryCalls)
|
||||
}
|
||||
|
||||
func newAuthenticatedHandler(t *testing.T) *gin.Engine {
|
||||
t.Helper()
|
||||
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
prevSecret := common.APIJWTSecret
|
||||
prevUser := common.APIUser
|
||||
prevPassword := common.APIPassword
|
||||
prevDisableAuth := common.DebugDisableAuth
|
||||
prevIssuerURL := common.OIDCIssuerURL
|
||||
prevSkipOriginCheck := common.APISkipOriginCheck
|
||||
|
||||
common.APIJWTSecret = []byte("0123456789abcdef0123456789abcdef")
|
||||
common.APIUser = "username"
|
||||
common.APIPassword = "password"
|
||||
common.DebugDisableAuth = false
|
||||
common.OIDCIssuerURL = ""
|
||||
common.APISkipOriginCheck = false
|
||||
|
||||
t.Cleanup(func() {
|
||||
common.APIJWTSecret = prevSecret
|
||||
common.APIUser = prevUser
|
||||
common.APIPassword = prevPassword
|
||||
common.DebugDisableAuth = prevDisableAuth
|
||||
common.OIDCIssuerURL = prevIssuerURL
|
||||
common.APISkipOriginCheck = prevSkipOriginCheck
|
||||
})
|
||||
|
||||
require.NoError(t, auth.Initialize())
|
||||
return NewHandler(true)
|
||||
}
|
||||
|
||||
func issueCSRFCookie(t *testing.T, handler http.Handler) *http.Cookie {
|
||||
t.Helper()
|
||||
|
||||
req := httptest.NewRequest(http.MethodHead, "/api/v1/auth/check", nil)
|
||||
req.Host = "app.example.com"
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
csrfCookie := findCookie(rec.Result().Cookies(), auth.CSRFCookieName)
|
||||
require.NotNil(t, csrfCookie)
|
||||
return csrfCookie
|
||||
}
|
||||
|
||||
func issueSessionToken(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
userpass, ok := auth.GetDefaultAuth().(*auth.UserPassAuth)
|
||||
require.True(t, ok)
|
||||
|
||||
token, err := userpass.NewToken()
|
||||
require.NoError(t, err)
|
||||
return token
|
||||
}
|
||||
|
||||
func newJSONRequest(t *testing.T, method, target string, body any) *http.Request {
|
||||
t.Helper()
|
||||
|
||||
encoded, err := json.Marshal(body)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(method, target, bytes.NewReader(encoded))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
}
|
||||
|
||||
func findCookie(cookies []*http.Cookie, name string) *http.Cookie {
|
||||
for _, cookie := range cookies {
|
||||
if cookie.Name == name {
|
||||
return cookie
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type stubAutocertProvider struct {
|
||||
forceExpiryCalls int
|
||||
}
|
||||
|
||||
func (p *stubAutocertProvider) GetCert(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (p *stubAutocertProvider) GetCertInfos() ([]autocert.CertInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (p *stubAutocertProvider) ScheduleRenewalAll(task.Parent) {}
|
||||
|
||||
func (p *stubAutocertProvider) ObtainCertAll() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *stubAutocertProvider) ForceExpiryAll() bool {
|
||||
p.forceExpiryCalls++
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *stubAutocertProvider) WaitRenewalDone(context.Context) bool {
|
||||
return true
|
||||
}
|
||||
@@ -56,11 +56,11 @@ func NewHandler(requireAuth bool) *gin.Engine {
|
||||
if auth.IsEnabled() && requireAuth {
|
||||
v1Auth := r.Group("/api/v1/auth")
|
||||
{
|
||||
v1Auth.HEAD("/check", CSRFMiddleware(), authApi.Check)
|
||||
v1Auth.POST("/login", CSRFMiddleware(), authApi.Login)
|
||||
v1Auth.HEAD("/check", authApi.Check)
|
||||
v1Auth.POST("/login", authApi.Login)
|
||||
v1Auth.GET("/callback", authApi.Callback)
|
||||
v1Auth.POST("/callback", CSRFMiddleware(), authApi.Callback)
|
||||
v1Auth.POST("/logout", CSRFMiddleware(), authApi.Logout)
|
||||
v1Auth.POST("/callback", authApi.Callback)
|
||||
v1Auth.POST("/logout", authApi.Logout)
|
||||
v1Auth.GET("/logout", authApi.Logout)
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,6 @@ func NewHandler(requireAuth bool) *gin.Engine {
|
||||
v1 := r.Group("/api/v1")
|
||||
if auth.IsEnabled() && requireAuth {
|
||||
v1.Use(AuthMiddleware())
|
||||
v1.Use(CSRFMiddleware())
|
||||
}
|
||||
if common.APISkipOriginCheck {
|
||||
v1.Use(SkipOriginCheckMiddleware())
|
||||
|
||||
@@ -115,7 +115,7 @@ No dedicated metrics exposed by handlers. Request metrics collected by middlewar
|
||||
|
||||
- All endpoints (except `/api/v1/version`) require authentication
|
||||
- Input validation using Gin binding tags
|
||||
- File read/write handlers are rooted per file type (`config/` or `config/middlewares/`) to prevent traversal into sibling paths
|
||||
- Path traversal prevention in file operations
|
||||
- WebSocket connections use same auth middleware as HTTP
|
||||
|
||||
## Failure Modes and Recovery
|
||||
@@ -195,3 +195,5 @@ func listContainers() ([]Container, error) {
|
||||
```bash
|
||||
curl http://localhost:8888/health
|
||||
```
|
||||
|
||||
)
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
// @Tags cert,websocket
|
||||
// @Produce plain
|
||||
// @Success 200 {object} apitypes.SuccessResponse
|
||||
// @Failure 400 {object} apitypes.ErrorResponse
|
||||
// @Failure 403 {object} apitypes.ErrorResponse
|
||||
// @Failure 500 {object} apitypes.ErrorResponse
|
||||
// @Router /cert/renew [get]
|
||||
|
||||
@@ -375,12 +375,6 @@
|
||||
"$ref": "#/definitions/SuccessResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
@@ -5131,7 +5125,10 @@
|
||||
"$ref": "#/definitions/MockResponse"
|
||||
},
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/routeApi.RawRule"
|
||||
},
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
}
|
||||
@@ -5441,12 +5438,6 @@
|
||||
],
|
||||
"x-nullable": true
|
||||
},
|
||||
"inbound_mtls_profile": {
|
||||
"description": "HTTP-based routes only: must match a configured inbound_mtls_profiles entry and is ignored when entrypoint.inbound_mtls_profile is set",
|
||||
"type": "string",
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
},
|
||||
"index": {
|
||||
"description": "Index file to serve for single-page app mode",
|
||||
"type": "string",
|
||||
@@ -5508,12 +5499,6 @@
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
},
|
||||
"relay_proxy_protocol_header": {
|
||||
"description": "TCP only: relay PROXY protocol header to the destination",
|
||||
"type": "boolean",
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
},
|
||||
"response_header_timeout": {
|
||||
"type": "integer",
|
||||
"x-nullable": false,
|
||||
@@ -6941,6 +6926,28 @@
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
},
|
||||
"routeApi.RawRule": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"do": {
|
||||
"type": "string",
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
},
|
||||
"on": {
|
||||
"type": "string",
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
}
|
||||
},
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
},
|
||||
"routeApi.RoutesByProvider": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
|
||||
@@ -905,7 +905,9 @@ definitions:
|
||||
mockResponse:
|
||||
$ref: '#/definitions/MockResponse'
|
||||
rules:
|
||||
type: string
|
||||
items:
|
||||
$ref: '#/definitions/routeApi.RawRule'
|
||||
type: array
|
||||
required:
|
||||
- rules
|
||||
type: object
|
||||
@@ -1052,10 +1054,6 @@ definitions:
|
||||
allOf:
|
||||
- $ref: '#/definitions/IdlewatcherConfig'
|
||||
x-nullable: true
|
||||
inbound_mtls_profile:
|
||||
description: 'HTTP-based routes only: must match a configured inbound_mtls_profiles
|
||||
entry and is ignored when entrypoint.inbound_mtls_profile is set'
|
||||
type: string
|
||||
index:
|
||||
description: Index file to serve for single-page app mode
|
||||
type: string
|
||||
@@ -1091,9 +1089,6 @@ definitions:
|
||||
x-nullable: true
|
||||
purl:
|
||||
type: string
|
||||
relay_proxy_protocol_header:
|
||||
description: 'TCP only: relay PROXY protocol header to the destination'
|
||||
type: boolean
|
||||
response_header_timeout:
|
||||
type: integer
|
||||
root:
|
||||
@@ -1840,12 +1835,12 @@ definitions:
|
||||
type: string
|
||||
kernel_version:
|
||||
type: string
|
||||
load_avg_5m:
|
||||
type: string
|
||||
load_avg_15m:
|
||||
type: string
|
||||
load_avg_1m:
|
||||
type: string
|
||||
load_avg_5m:
|
||||
type: string
|
||||
mem_pct:
|
||||
type: string
|
||||
mem_total:
|
||||
@@ -1863,6 +1858,15 @@ definitions:
|
||||
uptime:
|
||||
type: string
|
||||
type: object
|
||||
routeApi.RawRule:
|
||||
properties:
|
||||
do:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
"on":
|
||||
type: string
|
||||
type: object
|
||||
routeApi.RoutesByProvider:
|
||||
additionalProperties:
|
||||
items:
|
||||
@@ -2173,10 +2177,6 @@ paths:
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/SuccessResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/ErrorResponse'
|
||||
"403":
|
||||
description: Forbidden
|
||||
schema:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package fileapi
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
@@ -45,14 +44,7 @@ func Get(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
f, err := request.FileType.OpenFile(request.Filename, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to open root"))
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
content, err := io.ReadAll(f)
|
||||
content, err := os.ReadFile(request.FileType.GetPath(request.Filename))
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to read file"))
|
||||
return
|
||||
@@ -73,18 +65,9 @@ func GetFileType(file string) FileType {
|
||||
return FileTypeProvider
|
||||
}
|
||||
|
||||
func (t FileType) RootPath() string {
|
||||
func (t FileType) GetPath(filename string) string {
|
||||
if t == FileTypeMiddleware {
|
||||
return common.MiddlewareComposeBasePath
|
||||
return path.Join(common.MiddlewareComposeBasePath, filename)
|
||||
}
|
||||
return common.ConfigBasePath
|
||||
}
|
||||
|
||||
func (t FileType) OpenFile(filename string, flag int, perm os.FileMode) (*os.File, error) {
|
||||
root, err := os.OpenRoot(t.RootPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer root.Close()
|
||||
return root.OpenFile(filename, flag, perm)
|
||||
return path.Join(common.ConfigBasePath, filename)
|
||||
}
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
package fileapi_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
api "github.com/yusing/godoxy/internal/api"
|
||||
fileapi "github.com/yusing/godoxy/internal/api/v1/file"
|
||||
)
|
||||
|
||||
func setupFileAPITestRoot(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
oldWD, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
root := t.TempDir()
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(root, "config", "middlewares"), 0o755))
|
||||
require.NoError(t, os.Chdir(root))
|
||||
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, os.Chdir(oldWD))
|
||||
})
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
func newFileContentRouter() *gin.Engine {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
r := gin.New()
|
||||
r.Use(api.ErrorHandler())
|
||||
r.GET("/api/v1/file/content", fileapi.Get)
|
||||
r.PUT("/api/v1/file/content", fileapi.Set)
|
||||
return r
|
||||
}
|
||||
|
||||
func TestGet_PathTraversalBlocked(t *testing.T) {
|
||||
root := setupFileAPITestRoot(t)
|
||||
|
||||
const (
|
||||
insideFilename = "providers.yml"
|
||||
insideContent = "app: inside\n"
|
||||
outsideContent = "app: outside\n"
|
||||
)
|
||||
|
||||
require.NoError(t, os.WriteFile(filepath.Join(root, "config", insideFilename), []byte(insideContent), 0o644))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(root, "secret.yml"), []byte(outsideContent), 0o644))
|
||||
|
||||
r := newFileContentRouter()
|
||||
|
||||
t.Run("read_in_root_file", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/file/content?type=config&filename="+insideFilename, nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, insideContent, w.Body.String())
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
filename string
|
||||
queryEscaped bool
|
||||
}{
|
||||
{
|
||||
name: "dotdot_traversal_to_sibling_file",
|
||||
filename: "../secret.yml",
|
||||
},
|
||||
{
|
||||
name: "url_encoded_dotdot_traversal_to_sibling_file",
|
||||
filename: "../secret.yml",
|
||||
queryEscaped: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
filename := tt.filename
|
||||
if tt.queryEscaped {
|
||||
filename = url.QueryEscape(filename)
|
||||
}
|
||||
|
||||
url := "/api/v1/file/content?type=config&filename=" + filename
|
||||
req := httptest.NewRequest(http.MethodGet, url, nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
// "Blocked" means we should never successfully read the outside file.
|
||||
assert.NotEqual(t, http.StatusOK, w.Code)
|
||||
assert.NotEqual(t, outsideContent, w.Body.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -43,14 +43,7 @@ func Set(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
f, err := request.FileType.OpenFile(request.Filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to open file"))
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Write(content)
|
||||
err = os.WriteFile(request.FileType.GetPath(request.Filename), content, 0o644)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to write file"))
|
||||
return
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
package fileapi_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const validProviderYAML = `app:
|
||||
host: attacker.com
|
||||
port: 443
|
||||
scheme: https
|
||||
`
|
||||
|
||||
func TestSet_PathTraversalBlocked(t *testing.T) {
|
||||
root := setupFileAPITestRoot(t)
|
||||
r := newFileContentRouter()
|
||||
|
||||
t.Run("write_in_root_file", func(t *testing.T) {
|
||||
req := httptest.NewRequest(
|
||||
http.MethodPut,
|
||||
"/api/v1/file/content?type=provider&filename=providers.yml",
|
||||
strings.NewReader(validProviderYAML),
|
||||
)
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
content, err := os.ReadFile(filepath.Join(root, "config", "providers.yml"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, validProviderYAML, string(content))
|
||||
})
|
||||
|
||||
const originalContent = "do not overwrite\n"
|
||||
require.NoError(t, os.WriteFile(filepath.Join(root, "secret.yml"), []byte(originalContent), 0o644))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
filename string
|
||||
queryEscaped bool
|
||||
}{
|
||||
{
|
||||
name: "dotdot_traversal_to_sibling_file",
|
||||
filename: "../secret.yml",
|
||||
},
|
||||
{
|
||||
name: "url_encoded_dotdot_traversal_to_sibling_file",
|
||||
filename: "../secret.yml",
|
||||
queryEscaped: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
filename := tt.filename
|
||||
if tt.queryEscaped {
|
||||
filename = url.QueryEscape(filename)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(
|
||||
http.MethodPut,
|
||||
"/api/v1/file/content?type=provider&filename="+filename,
|
||||
strings.NewReader(validProviderYAML),
|
||||
)
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.NotEqual(t, http.StatusOK, w.Code)
|
||||
|
||||
content, err := os.ReadFile(filepath.Join(root, "secret.yml"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, originalContent, string(content))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/cenkalti/backoff/v5"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -240,7 +241,7 @@ func marshalSystemInfo(ws *websocket.Manager, agentName string, systemInfo any)
|
||||
defer bufFromPool.release(bufFromPool.RawMessage)
|
||||
}
|
||||
|
||||
err := json.NewEncoder(buf).Encode(map[string]any{
|
||||
err := sonic.ConfigDefault.NewEncoder(buf).Encode(map[string]any{
|
||||
agentName: systemInfo,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
"github.com/yusing/godoxy/internal/route/rules"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
@@ -24,7 +23,7 @@ type RawRule struct {
|
||||
}
|
||||
|
||||
type PlaygroundRequest struct {
|
||||
Rules string `json:"rules" binding:"required"`
|
||||
Rules []RawRule `json:"rules" binding:"required"`
|
||||
MockRequest MockRequest `json:"mockRequest"`
|
||||
MockResponse MockResponse `json:"mockResponse"`
|
||||
} // @name PlaygroundRequest
|
||||
@@ -256,35 +255,7 @@ func handlerWithRecover(w http.ResponseWriter, r *http.Request, h http.HandlerFu
|
||||
h(w, r)
|
||||
}
|
||||
|
||||
func parseRules(config string) ([]ParsedRule, rules.Rules, error) {
|
||||
config = strings.TrimSpace(config)
|
||||
if config == "" {
|
||||
return []ParsedRule{}, nil, nil
|
||||
}
|
||||
|
||||
var rawRules []RawRule
|
||||
if err := yaml.Unmarshal([]byte(config), &rawRules); err == nil && len(rawRules) > 0 {
|
||||
return parseRawRules(rawRules)
|
||||
}
|
||||
|
||||
var rulesList rules.Rules
|
||||
if err := rulesList.Parse(config); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
parsedRules := make([]ParsedRule, 0, len(rulesList))
|
||||
for _, rule := range rulesList {
|
||||
parsedRules = append(parsedRules, ParsedRule{
|
||||
Name: rule.Name,
|
||||
On: rule.On.String(),
|
||||
Do: rule.Do.String(),
|
||||
})
|
||||
}
|
||||
|
||||
return parsedRules, rulesList, nil
|
||||
}
|
||||
|
||||
func parseRawRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, error) {
|
||||
func parseRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, error) {
|
||||
parsedRules := make([]ParsedRule, 0, len(rawRules))
|
||||
rulesList := make(rules.Rules, 0, len(rawRules))
|
||||
|
||||
|
||||
@@ -22,10 +22,13 @@ func TestPlayground(t *testing.T) {
|
||||
{
|
||||
name: "simple path matching rule",
|
||||
request: PlaygroundRequest{
|
||||
Rules: `- name: test rule
|
||||
on: path /api
|
||||
do: pass
|
||||
`,
|
||||
Rules: []RawRule{
|
||||
{
|
||||
Name: "test rule",
|
||||
On: "path /api",
|
||||
Do: "pass",
|
||||
},
|
||||
},
|
||||
MockRequest: MockRequest{
|
||||
Method: "GET",
|
||||
Path: "/api",
|
||||
@@ -50,10 +53,13 @@ func TestPlayground(t *testing.T) {
|
||||
{
|
||||
name: "header matching rule",
|
||||
request: PlaygroundRequest{
|
||||
Rules: `- name: check user agent
|
||||
on: header User-Agent Chrome
|
||||
do: error 403 Forbidden
|
||||
`,
|
||||
Rules: []RawRule{
|
||||
{
|
||||
Name: "check user agent",
|
||||
On: "header User-Agent Chrome",
|
||||
Do: "error 403 Forbidden",
|
||||
},
|
||||
},
|
||||
MockRequest: MockRequest{
|
||||
Method: "GET",
|
||||
Path: "/",
|
||||
@@ -84,10 +90,13 @@ func TestPlayground(t *testing.T) {
|
||||
{
|
||||
name: "invalid rule syntax",
|
||||
request: PlaygroundRequest{
|
||||
Rules: `- name: bad rule
|
||||
on: invalid_checker something
|
||||
do: pass
|
||||
`,
|
||||
Rules: []RawRule{
|
||||
{
|
||||
Name: "bad rule",
|
||||
On: "invalid_checker something",
|
||||
Do: "pass",
|
||||
},
|
||||
},
|
||||
MockRequest: MockRequest{
|
||||
Method: "GET",
|
||||
Path: "/",
|
||||
@@ -106,10 +115,13 @@ func TestPlayground(t *testing.T) {
|
||||
{
|
||||
name: "rewrite path rule",
|
||||
request: PlaygroundRequest{
|
||||
Rules: `- name: rewrite rule
|
||||
on: path glob(/api/*)
|
||||
do: rewrite /api/ /v1/
|
||||
`,
|
||||
Rules: []RawRule{
|
||||
{
|
||||
Name: "rewrite rule",
|
||||
On: "path glob(/api/*)",
|
||||
Do: "rewrite /api/ /v1/",
|
||||
},
|
||||
},
|
||||
MockRequest: MockRequest{
|
||||
Method: "GET",
|
||||
Path: "/api/users",
|
||||
@@ -136,10 +148,13 @@ func TestPlayground(t *testing.T) {
|
||||
{
|
||||
name: "method matching rule",
|
||||
request: PlaygroundRequest{
|
||||
Rules: `- name: block POST
|
||||
on: method POST
|
||||
do: error "405" "Method Not Allowed"
|
||||
`,
|
||||
Rules: []RawRule{
|
||||
{
|
||||
Name: "block POST",
|
||||
On: "method POST",
|
||||
Do: `error "405" "Method Not Allowed"`,
|
||||
},
|
||||
},
|
||||
MockRequest: MockRequest{
|
||||
Method: "POST",
|
||||
Path: "/api",
|
||||
@@ -158,63 +173,6 @@ func TestPlayground(t *testing.T) {
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "block syntax default rule",
|
||||
request: PlaygroundRequest{
|
||||
Rules: `default {
|
||||
pass
|
||||
}`,
|
||||
MockRequest: MockRequest{
|
||||
Method: "GET",
|
||||
Path: "/",
|
||||
},
|
||||
},
|
||||
wantStatusCode: http.StatusOK,
|
||||
checkResponse: func(t *testing.T, resp PlaygroundResponse) {
|
||||
if len(resp.ParsedRules) != 1 {
|
||||
t.Errorf("expected 1 parsed rule, got %d", len(resp.ParsedRules))
|
||||
}
|
||||
if resp.ParsedRules[0].ValidationError != nil {
|
||||
t.Errorf("expected rule to be valid, got error: %v", resp.ParsedRules[0].ValidationError)
|
||||
}
|
||||
if !resp.UpstreamCalled {
|
||||
t.Error("expected upstream to be called")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "block syntax conditional rule",
|
||||
request: PlaygroundRequest{
|
||||
Rules: `header User-Agent Chrome {
|
||||
error 403 Forbidden
|
||||
}`,
|
||||
MockRequest: MockRequest{
|
||||
Method: "GET",
|
||||
Path: "/",
|
||||
Headers: map[string][]string{
|
||||
"User-Agent": {"Chrome"},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantStatusCode: http.StatusOK,
|
||||
checkResponse: func(t *testing.T, resp PlaygroundResponse) {
|
||||
if len(resp.ParsedRules) != 1 {
|
||||
t.Errorf("expected 1 parsed rule, got %d", len(resp.ParsedRules))
|
||||
}
|
||||
if resp.ParsedRules[0].ValidationError != nil {
|
||||
t.Errorf("expected rule to be valid, got error: %v", resp.ParsedRules[0].ValidationError)
|
||||
}
|
||||
if len(resp.MatchedRules) != 1 {
|
||||
t.Errorf("expected 1 matched rule, got %d", len(resp.MatchedRules))
|
||||
}
|
||||
if resp.FinalResponse.StatusCode != http.StatusForbidden {
|
||||
t.Errorf("expected status 403, got %d", resp.FinalResponse.StatusCode)
|
||||
}
|
||||
if resp.UpstreamCalled {
|
||||
t.Error("expected upstream not to be called")
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed block_page.min.html
|
||||
//go:embed block_page.html
|
||||
var blockPageHTML string
|
||||
|
||||
var blockPageTemplate = template.Must(template.New("block_page").Parse(blockPageHTML))
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<meta charset="UTF-8" /><meta
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1.0"
|
||||
/><title>Access Denied</title
|
||||
><meta name="color-scheme" content="dark" /><style>
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
--bg0: #070a12;
|
||||
--bg1: #0b1020;
|
||||
--card: #ffffff0e;
|
||||
--card2: #ffffff0d;
|
||||
--text: #ffffffeb;
|
||||
--muted: #ffffffad;
|
||||
--border: #ffffff1f;
|
||||
--borderSoft: #ffffff14;
|
||||
--borderStrong: #ffffff24;
|
||||
--borderHover: #ffffff38;
|
||||
--shadow: 0 22px 60px #0000008c;
|
||||
--shadowCard: 0 22px 60px #00000094;
|
||||
--shadowButton: 0 12px 28px #00000059;
|
||||
--insetHighlight: inset 0 1px 0 #ffffff0a;
|
||||
--ring: #78a0d26b;
|
||||
--accent0: #7aa3c8;
|
||||
--accent1: #9a8bc7;
|
||||
--btn: #ffffff0f;
|
||||
--btnHover: #ffffff14;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
color: var(--text);
|
||||
background-color: var(--bg1);
|
||||
background-image: none;
|
||||
margin: 0;
|
||||
font-family:
|
||||
ui-sans-serif,
|
||||
system-ui,
|
||||
-apple-system,
|
||||
Segoe UI,
|
||||
Roboto,
|
||||
Helvetica,
|
||||
Arial,
|
||||
Apple Color Emoji,
|
||||
Segoe UI Emoji;
|
||||
}
|
||||
.wrap {
|
||||
place-items: center;
|
||||
min-height: 100%;
|
||||
padding: 28px 16px;
|
||||
display: grid;
|
||||
}
|
||||
.card {
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
width: min(720px, 100%);
|
||||
box-shadow: var(--shadowCard), var(--insetHighlight);
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.topbar {
|
||||
border-bottom: 1px solid var(--borderSoft);
|
||||
background: var(--card2);
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 18px 18px 12px;
|
||||
display: flex;
|
||||
}
|
||||
.badge {
|
||||
border: 1px solid var(--borderStrong);
|
||||
background: var(--card2);
|
||||
border-radius: 12px;
|
||||
place-items: center;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
display: grid;
|
||||
}
|
||||
.badge svg {
|
||||
opacity: 0.95;
|
||||
}
|
||||
.badge .bang {
|
||||
color: #ffffffe6;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
h1 {
|
||||
letter-spacing: 0.2px;
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.sub {
|
||||
color: var(--muted);
|
||||
margin: 2px 0 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
.content {
|
||||
padding: 18px;
|
||||
}
|
||||
.error {
|
||||
color: #fffc;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
text-transform: capitalize;
|
||||
background: #00000040;
|
||||
border: 1px solid #ffffff1a;
|
||||
border-radius: 12px;
|
||||
margin: 0;
|
||||
padding: 14px;
|
||||
font-family:
|
||||
ui-monospace,
|
||||
SFMono-Regular,
|
||||
Menlo,
|
||||
Monaco,
|
||||
Consolas,
|
||||
Liberation Mono,
|
||||
Courier New,
|
||||
monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.actions {
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 14px;
|
||||
display: flex;
|
||||
}
|
||||
a.button {
|
||||
color: #ffffffeb;
|
||||
border: 1px solid var(--borderStrong);
|
||||
background: var(--btn);
|
||||
box-shadow: var(--shadowButton);
|
||||
border-radius: 10px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
transition:
|
||||
transform 0.12s,
|
||||
border-color 0.12s,
|
||||
background 0.12s,
|
||||
box-shadow 0.12s;
|
||||
display: inline-flex;
|
||||
}
|
||||
a.button:hover {
|
||||
border-color: var(--borderHover);
|
||||
background: var(--btnHover);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
a.button:focus-visible {
|
||||
box-shadow:
|
||||
0 0 0 3px var(--ring),
|
||||
var(--shadowButton);
|
||||
outline: 0;
|
||||
}
|
||||
.hint {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.hint kbd {
|
||||
border: 1px solid var(--borderStrong);
|
||||
background: var(--btn);
|
||||
color: #ffffffdb;
|
||||
border-radius: 6px;
|
||||
padding: 2px 4px;
|
||||
font-family:
|
||||
ui-monospace,
|
||||
SFMono-Regular,
|
||||
Menlo,
|
||||
Monaco,
|
||||
Consolas,
|
||||
Liberation Mono,
|
||||
Courier New,
|
||||
monospace;
|
||||
font-size: 11px;
|
||||
}
|
||||
kbd {
|
||||
font-weight: 500;
|
||||
}
|
||||
.kbd-container {
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
display: inline-flex;
|
||||
}
|
||||
</style>
|
||||
<div class="wrap">
|
||||
<main class="card" role="main" aria-labelledby="title">
|
||||
<header class="topbar">
|
||||
<div class="badge" aria-hidden="true"><span class="bang">!</span></div>
|
||||
<div>
|
||||
<h1 id="title">{{.StatusText}}</h1>
|
||||
<p class="sub">You don’t have permission to access this resource.</p>
|
||||
</div>
|
||||
</header>
|
||||
<section class="content">
|
||||
<pre class="error">{{.Error}}</pre>
|
||||
<div class="actions">
|
||||
<a class="button" href="{{.ActionURL}}"
|
||||
><span>{{.ActionText}}</span><span aria-hidden="true">→</span></a
|
||||
>
|
||||
<div class="hint">
|
||||
If you just signed in, try refreshing the page.<span
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<div class="kbd-container">
|
||||
<kbd>Ctrl</kbd><span>+</span><kbd>R</kbd>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</html>
|
||||
@@ -1,84 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
const (
|
||||
CSRFCookieName = "godoxy_csrf"
|
||||
CSRFHKDFSalt = "godoxy-csrf"
|
||||
CSRFHeaderName = "X-CSRF-Token"
|
||||
csrfTokenLength = 32
|
||||
)
|
||||
|
||||
// csrfSecret is derived from API_JWT_SECRET via HKDF for cryptographic
|
||||
// separation from JWT signing. Falls back to an ephemeral random key
|
||||
// for OIDC-only setups where no JWT secret is configured.
|
||||
var csrfSecret = func() []byte {
|
||||
if common.APIJWTSecret != nil {
|
||||
return hkdf.Extract(sha256.New, common.APIJWTSecret, []byte(CSRFHKDFSalt))
|
||||
}
|
||||
b := make([]byte, 32)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
panic("failed to generate CSRF secret: " + err.Error())
|
||||
}
|
||||
return b
|
||||
}()
|
||||
|
||||
func GenerateCSRFToken() (string, error) {
|
||||
nonce := make([]byte, csrfTokenLength)
|
||||
if _, err := rand.Read(nonce); err != nil {
|
||||
return "", err
|
||||
}
|
||||
nonceHex := hex.EncodeToString(nonce)
|
||||
return nonceHex + "." + csrfSign(nonceHex), nil
|
||||
}
|
||||
|
||||
// ValidateCSRFToken checks the HMAC signature embedded in the token.
|
||||
// This prevents subdomain cookie-injection attacks where an attacker
|
||||
// sets a forged CSRF cookie — they cannot produce a valid signature
|
||||
// without the ephemeral secret.
|
||||
func ValidateCSRFToken(token string) bool {
|
||||
nonce, sig, ok := strings.Cut(token, ".")
|
||||
if !ok || len(nonce) != csrfTokenLength*2 {
|
||||
return false
|
||||
}
|
||||
return hmac.Equal([]byte(sig), []byte(csrfSign(nonce)))
|
||||
}
|
||||
|
||||
func csrfSign(nonce string) string {
|
||||
mac := hmac.New(sha256.New, csrfSecret)
|
||||
mac.Write([]byte(nonce))
|
||||
return hex.EncodeToString(mac.Sum(nil))
|
||||
}
|
||||
|
||||
func SetCSRFCookie(w http.ResponseWriter, r *http.Request, token string) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: CSRFCookieName,
|
||||
Value: token,
|
||||
HttpOnly: false,
|
||||
Secure: common.APIJWTSecure,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
Path: "/",
|
||||
})
|
||||
}
|
||||
|
||||
func ClearCSRFCookie(w http.ResponseWriter, r *http.Request) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: CSRFCookieName,
|
||||
Value: "",
|
||||
MaxAge: -1,
|
||||
HttpOnly: false,
|
||||
Secure: common.APIJWTSecure,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
Path: "/",
|
||||
})
|
||||
}
|
||||
@@ -216,7 +216,7 @@ func TestOIDCCallbackHandler(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/auth/callback?code="+tt.code+"&state="+tt.state, nil)
|
||||
if tt.state != "" {
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: defaultAuth.(*OIDCProvider).getAppScopedCookieName(CookieOauthState),
|
||||
Name: CookieOauthState,
|
||||
Value: tt.state,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"encoding/json"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
httputils "github.com/yusing/goutils/http"
|
||||
@@ -107,7 +107,7 @@ type UserPassAuthCallbackRequest struct {
|
||||
|
||||
func (auth *UserPassAuth) PostAuthCallbackHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var creds UserPassAuthCallbackRequest
|
||||
err := json.NewDecoder(r.Body).Decode(&creds)
|
||||
err := sonic.ConfigDefault.NewDecoder(r.Body).Decode(&creds)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid request", http.StatusBadRequest)
|
||||
return
|
||||
|
||||
@@ -151,13 +151,6 @@ flowchart TD
|
||||
style U fill:#84261A,color:#fff
|
||||
```
|
||||
|
||||
### Concurrency Model
|
||||
|
||||
- Certificate obtain / renew work is serialized per provider.
|
||||
- Extra providers may still renew in parallel with each other.
|
||||
- Each provider uses its own ACME HTTP client instance so parallel renewals do not mutate shared transport state.
|
||||
- TLS certificate replacement and SNI matcher rebuilds are synchronized before new state becomes visible to handshakes.
|
||||
|
||||
### SNI Matching Flow
|
||||
|
||||
```mermaid
|
||||
|
||||
@@ -198,7 +198,7 @@ func (cfg *Config) GetLegoConfig() (*User, *lego.Config, error) {
|
||||
legoCfg.Certificate.KeyType = certcrypto.EC256
|
||||
|
||||
if cfg.HTTPClient != nil {
|
||||
legoCfg.HTTPClient = cloneHTTPClient(cfg.HTTPClient)
|
||||
legoCfg.HTTPClient = cfg.HTTPClient
|
||||
}
|
||||
|
||||
if cfg.CADirURL != "" {
|
||||
@@ -216,18 +216,6 @@ func (cfg *Config) GetLegoConfig() (*User, *lego.Config, error) {
|
||||
return user, legoCfg, nil
|
||||
}
|
||||
|
||||
func cloneHTTPClient(client *http.Client) *http.Client {
|
||||
if client == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
clone := *client
|
||||
if transport, ok := client.Transport.(*http.Transport); ok && transport != nil {
|
||||
clone.Transport = transport.Clone()
|
||||
}
|
||||
return &clone
|
||||
}
|
||||
|
||||
func MergeExtraConfig(mainCfg *Config, extraCfg *ConfigExtra) ConfigExtra {
|
||||
merged := ConfigExtra(*mainCfg)
|
||||
merged.Extra = nil
|
||||
|
||||
@@ -32,9 +32,6 @@ import (
|
||||
|
||||
type (
|
||||
Provider struct {
|
||||
mu sync.RWMutex
|
||||
obtain sync.Mutex
|
||||
|
||||
logger zerolog.Logger
|
||||
|
||||
cfg *Config
|
||||
@@ -99,36 +96,32 @@ func NewProvider(cfg *Config, user *User, legoCfg *lego.Config) (*Provider, erro
|
||||
}
|
||||
|
||||
func (p *Provider) GetCert(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
tlsCert := p.getTLSCert()
|
||||
if tlsCert == nil {
|
||||
if p.tlsCert == nil {
|
||||
return nil, ErrNoCertificates
|
||||
}
|
||||
if hello == nil || hello.ServerName == "" {
|
||||
return tlsCert, nil
|
||||
return p.tlsCert, nil
|
||||
}
|
||||
if prov := p.getSNIMatcher().match(hello.ServerName); prov != nil {
|
||||
if cert := prov.getTLSCert(); cert != nil {
|
||||
return cert, nil
|
||||
if prov := p.sniMatcher.match(hello.ServerName); prov != nil && prov.tlsCert != nil {
|
||||
return prov.tlsCert, nil
|
||||
}
|
||||
}
|
||||
return tlsCert, nil
|
||||
return p.tlsCert, nil
|
||||
}
|
||||
|
||||
func (p *Provider) GetCertInfos() ([]autocert.CertInfo, error) {
|
||||
allProviders := p.allProviders()
|
||||
certInfos := make([]autocert.CertInfo, 0, len(allProviders))
|
||||
for _, provider := range allProviders {
|
||||
tlsCert := provider.getTLSCert()
|
||||
if tlsCert == nil || tlsCert.Leaf == nil {
|
||||
if provider.tlsCert == nil {
|
||||
continue
|
||||
}
|
||||
certInfos = append(certInfos, autocert.CertInfo{
|
||||
Subject: tlsCert.Leaf.Subject.CommonName,
|
||||
Issuer: tlsCert.Leaf.Issuer.CommonName,
|
||||
NotBefore: tlsCert.Leaf.NotBefore.Unix(),
|
||||
NotAfter: tlsCert.Leaf.NotAfter.Unix(),
|
||||
DNSNames: tlsCert.Leaf.DNSNames,
|
||||
EmailAddresses: tlsCert.Leaf.EmailAddresses,
|
||||
Subject: provider.tlsCert.Leaf.Subject.CommonName,
|
||||
Issuer: provider.tlsCert.Leaf.Issuer.CommonName,
|
||||
NotBefore: provider.tlsCert.Leaf.NotBefore.Unix(),
|
||||
NotAfter: provider.tlsCert.Leaf.NotAfter.Unix(),
|
||||
DNSNames: provider.tlsCert.Leaf.DNSNames,
|
||||
EmailAddresses: provider.tlsCert.Leaf.EmailAddresses,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -158,9 +151,7 @@ func (p *Provider) GetKeyPath() string {
|
||||
}
|
||||
|
||||
func (p *Provider) GetExpiries() CertExpiries {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
return maps.Clone(p.certExpiries)
|
||||
return p.certExpiries
|
||||
}
|
||||
|
||||
func (p *Provider) GetLastFailure() (time.Time, error) {
|
||||
@@ -168,25 +159,17 @@ func (p *Provider) GetLastFailure() (time.Time, error) {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
|
||||
p.mu.RLock()
|
||||
lastFailure := p.lastFailure
|
||||
p.mu.RUnlock()
|
||||
|
||||
if lastFailure.IsZero() {
|
||||
if p.lastFailure.IsZero() {
|
||||
data, err := os.ReadFile(p.lastFailureFile)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return time.Time{}, err
|
||||
}
|
||||
} else {
|
||||
parsed, _ := time.Parse(time.RFC3339, string(data))
|
||||
p.mu.Lock()
|
||||
p.lastFailure = parsed
|
||||
lastFailure = p.lastFailure
|
||||
p.mu.Unlock()
|
||||
p.lastFailure, _ = time.Parse(time.RFC3339, string(data))
|
||||
}
|
||||
}
|
||||
return lastFailure, nil
|
||||
return p.lastFailure, nil
|
||||
}
|
||||
|
||||
func (p *Provider) UpdateLastFailure() error {
|
||||
@@ -194,9 +177,7 @@ func (p *Provider) UpdateLastFailure() error {
|
||||
return nil
|
||||
}
|
||||
t := time.Now()
|
||||
p.mu.Lock()
|
||||
p.lastFailure = t
|
||||
p.mu.Unlock()
|
||||
return os.WriteFile(p.lastFailureFile, t.AppendFormat(nil, time.RFC3339), 0o600)
|
||||
}
|
||||
|
||||
@@ -204,9 +185,7 @@ func (p *Provider) ClearLastFailure() error {
|
||||
if common.IsTest {
|
||||
return nil
|
||||
}
|
||||
p.mu.Lock()
|
||||
p.lastFailure = time.Time{}
|
||||
p.mu.Unlock()
|
||||
err := os.Remove(p.lastFailureFile)
|
||||
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
return err
|
||||
@@ -280,9 +259,6 @@ func (p *Provider) ObtainCertAll() error {
|
||||
|
||||
// ObtainCert renews existing certificate or obtains a new certificate for this provider.
|
||||
func (p *Provider) ObtainCert() error {
|
||||
p.obtain.Lock()
|
||||
defer p.obtain.Unlock()
|
||||
|
||||
if p.cfg.Provider == ProviderLocal {
|
||||
return nil
|
||||
}
|
||||
@@ -296,19 +272,10 @@ func (p *Provider) ObtainCert() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.mu.RLock()
|
||||
client := p.client
|
||||
userRegistered := p.user.Registration != nil
|
||||
legoCert := p.legoCert
|
||||
p.mu.RUnlock()
|
||||
|
||||
if client == nil {
|
||||
if p.client == nil {
|
||||
if err := p.initClient(); err != nil {
|
||||
return err
|
||||
}
|
||||
p.mu.RLock()
|
||||
client = p.client
|
||||
p.mu.RUnlock()
|
||||
}
|
||||
|
||||
// mark it as failed first, clear it later if successful
|
||||
@@ -318,7 +285,7 @@ func (p *Provider) ObtainCert() error {
|
||||
return fmt.Errorf("failed to update last failure: %w", err)
|
||||
}
|
||||
|
||||
if !userRegistered {
|
||||
if p.user.Registration == nil {
|
||||
if err := p.registerACME(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -327,24 +294,20 @@ func (p *Provider) ObtainCert() error {
|
||||
var cert *certificate.Resource
|
||||
var err error
|
||||
|
||||
if legoCert != nil {
|
||||
cert, err = client.Certificate.RenewWithOptions(*legoCert, &certificate.RenewOptions{
|
||||
if p.legoCert != nil {
|
||||
cert, err = p.client.Certificate.RenewWithOptions(*p.legoCert, &certificate.RenewOptions{
|
||||
Bundle: true,
|
||||
})
|
||||
if err != nil {
|
||||
p.mu.Lock()
|
||||
p.legoCert = nil
|
||||
p.mu.Unlock()
|
||||
log.Err(err).Msg("cert renew failed, fallback to obtain")
|
||||
} else {
|
||||
p.mu.Lock()
|
||||
p.legoCert = cert
|
||||
p.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
if cert == nil {
|
||||
cert, err = client.Certificate.Obtain(certificate.ObtainRequest{
|
||||
cert, err = p.client.Certificate.Obtain(certificate.ObtainRequest{
|
||||
Domains: p.cfg.Domains,
|
||||
Bundle: true,
|
||||
})
|
||||
@@ -366,10 +329,8 @@ func (p *Provider) ObtainCert() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.mu.Lock()
|
||||
p.tlsCert = &tlsCert
|
||||
p.certExpiries = expiries
|
||||
p.mu.Unlock()
|
||||
p.rebuildSNIMatcher()
|
||||
|
||||
if err := p.ClearLastFailure(); err != nil {
|
||||
@@ -400,10 +361,8 @@ func (p *Provider) loadCert() error {
|
||||
return err
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
p.tlsCert = &cert
|
||||
p.certExpiries = expiries
|
||||
p.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -411,7 +370,7 @@ func (p *Provider) loadCert() error {
|
||||
// PrintCertExpiriesAll prints the certificate expiries for this provider and all extra providers.
|
||||
func (p *Provider) PrintCertExpiriesAll() {
|
||||
for _, provider := range p.allProviders() {
|
||||
for domain, expiry := range provider.GetExpiries() {
|
||||
for domain, expiry := range provider.certExpiries {
|
||||
p.logger.Info().Str("domain", domain).Msgf("certificate expire on %s", strutils.FormatTime(expiry))
|
||||
}
|
||||
}
|
||||
@@ -419,8 +378,6 @@ func (p *Provider) PrintCertExpiriesAll() {
|
||||
|
||||
// ShouldRenewOn returns the time at which the certificate should be renewed.
|
||||
func (p *Provider) ShouldRenewOn() time.Time {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
for _, expiry := range p.certExpiries {
|
||||
return expiry.AddDate(0, -1, 0) // 1 month before
|
||||
}
|
||||
@@ -560,22 +517,16 @@ func (p *Provider) initClient() error {
|
||||
return err
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
p.client = legoClient
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) registerACME() error {
|
||||
p.mu.RLock()
|
||||
registrationExists := p.user.Registration != nil
|
||||
client := p.client
|
||||
p.mu.RUnlock()
|
||||
if registrationExists {
|
||||
if p.user.Registration != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
reg, err := client.Registration.ResolveAccountByKey()
|
||||
reg, err := p.client.Registration.ResolveAccountByKey()
|
||||
if err == nil {
|
||||
p.user.Registration = reg
|
||||
log.Info().Msg("reused acme registration from private key")
|
||||
@@ -583,20 +534,18 @@ func (p *Provider) registerACME() error {
|
||||
}
|
||||
|
||||
if p.cfg.EABKid != "" && p.cfg.EABHmac != "" {
|
||||
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||
reg, err = p.client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||
TermsOfServiceAgreed: true,
|
||||
Kid: p.cfg.EABKid,
|
||||
HmacEncoded: p.cfg.EABHmac,
|
||||
})
|
||||
} else {
|
||||
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
reg, err = p.client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.mu.Lock()
|
||||
p.user.Registration = reg
|
||||
p.mu.Unlock()
|
||||
log.Info().Interface("reg", reg).Msg("acme registered")
|
||||
return nil
|
||||
}
|
||||
@@ -630,9 +579,6 @@ func (p *Provider) saveCert(cert *certificate.Resource) error {
|
||||
}
|
||||
|
||||
func (p *Provider) certState() CertState {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
|
||||
if time.Now().After(p.ShouldRenewOn()) {
|
||||
return CertStateExpired
|
||||
}
|
||||
@@ -720,26 +666,9 @@ func (p *Provider) rebuildSNIMatcher() {
|
||||
return
|
||||
}
|
||||
|
||||
matcher := sniMatcher{}
|
||||
matcher.addProvider(p)
|
||||
p.sniMatcher = sniMatcher{}
|
||||
p.sniMatcher.addProvider(p)
|
||||
for _, ep := range p.extraProviders {
|
||||
matcher.addProvider(ep)
|
||||
p.sniMatcher.addProvider(ep)
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
p.sniMatcher = matcher
|
||||
p.mu.Unlock()
|
||||
}
|
||||
|
||||
func (p *Provider) getSNIMatcher() *sniMatcher {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
matcher := p.sniMatcher
|
||||
return &matcher
|
||||
}
|
||||
|
||||
func (p *Provider) getTLSCert() *tls.Certificate {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
return p.tlsCert
|
||||
}
|
||||
|
||||
@@ -62,14 +62,10 @@ func normalizeServerName(s string) string {
|
||||
}
|
||||
|
||||
func (m *sniMatcher) addProvider(p *Provider) {
|
||||
if p == nil {
|
||||
if p == nil || p.tlsCert == nil || len(p.tlsCert.Certificate) == 0 {
|
||||
return
|
||||
}
|
||||
tlsCert := p.getTLSCert()
|
||||
if tlsCert == nil || len(tlsCert.Certificate) == 0 {
|
||||
return
|
||||
}
|
||||
leaf, err := x509.ParseCertificate(tlsCert.Certificate[0])
|
||||
leaf, err := x509.ParseCertificate(p.tlsCert.Certificate[0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ var (
|
||||
LocalAPIHTTPHost,
|
||||
LocalAPIHTTPPort,
|
||||
LocalAPIHTTPURL = env.GetAddrEnv("LOCAL_API_ADDR", "", "http")
|
||||
LocalAPIAllowNonLoopback = env.GetEnvBool("LOCAL_API_ALLOW_NON_LOOPBACK", false)
|
||||
|
||||
APIJWTSecure = env.GetEnvBool("API_JWT_SECURE", true)
|
||||
APIJWTSecret = decodeJWTKey(env.GetEnvString("API_JWT_SECRET", ""))
|
||||
|
||||
@@ -30,7 +30,6 @@ type Config struct {
|
||||
ACL *acl.Config
|
||||
AutoCert *autocert.Config
|
||||
Entrypoint entrypoint.Config
|
||||
InboundMTLSProfiles map[string]types.InboundMTLSProfile
|
||||
Providers Providers
|
||||
MatchDomains []string
|
||||
Homepage homepage.Config
|
||||
@@ -72,8 +71,6 @@ type State interface {
|
||||
}
|
||||
```
|
||||
|
||||
`StartAPIServers` starts the authenticated API from `common.APIHTTPAddr` and, when `LOCAL_API_ADDR` is set, an additional **unauthenticated** local listener from `common.LocalAPIHTTPAddr`. That address is validated for loopback binds (with optional DNS resolution); non-loopback requires `LOCAL_API_ALLOW_NON_LOOPBACK` and logs a warning. See [`internal/api/v1/README.md`](../api/v1/README.md#configuration-surface).
|
||||
|
||||
### Exported functions
|
||||
|
||||
```go
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
package config_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"iter"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
config "github.com/yusing/godoxy/internal/config/types"
|
||||
entrypointtypes "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
routeimpl "github.com/yusing/godoxy/internal/route"
|
||||
route "github.com/yusing/godoxy/internal/route/types"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
"github.com/yusing/goutils/server"
|
||||
"github.com/yusing/goutils/task"
|
||||
)
|
||||
|
||||
func TestRouteValidateInboundMTLSProfile(t *testing.T) {
|
||||
prev := config.WorkingState.Load()
|
||||
t.Cleanup(func() {
|
||||
if prev != nil {
|
||||
config.WorkingState.Store(prev)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("rejects unknown profile", func(t *testing.T) {
|
||||
state := &stubState{cfg: &config.Config{
|
||||
InboundMTLSProfiles: map[string]types.InboundMTLSProfile{
|
||||
"known": {UseSystemCAs: true},
|
||||
},
|
||||
}}
|
||||
config.WorkingState.Store(state)
|
||||
|
||||
r := &routeimpl.Route{
|
||||
Alias: "test",
|
||||
Scheme: route.SchemeHTTP,
|
||||
Host: "example.com",
|
||||
Port: route.Port{Proxy: 80},
|
||||
InboundMTLSProfile: "missing",
|
||||
}
|
||||
err := r.Validate()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, `inbound mTLS profile "missing" not found`)
|
||||
})
|
||||
|
||||
t.Run("rejects route profile when global profile configured", func(t *testing.T) {
|
||||
state := &stubState{cfg: &config.Config{
|
||||
InboundMTLSProfiles: map[string]types.InboundMTLSProfile{
|
||||
"corp": {UseSystemCAs: true},
|
||||
},
|
||||
}}
|
||||
state.cfg.Entrypoint.InboundMTLSProfile = "corp"
|
||||
config.WorkingState.Store(state)
|
||||
|
||||
r := &routeimpl.Route{
|
||||
Alias: "test",
|
||||
Scheme: route.SchemeHTTP,
|
||||
Host: "example.com",
|
||||
Port: route.Port{Proxy: 80},
|
||||
InboundMTLSProfile: "corp",
|
||||
}
|
||||
err := r.Validate()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "route inbound_mtls_profile is not supported")
|
||||
})
|
||||
}
|
||||
|
||||
type stubState struct {
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func (s *stubState) InitFromFile(string) error { return nil }
|
||||
func (s *stubState) Init([]byte) error { return nil }
|
||||
func (s *stubState) Task() *task.Task { return nil }
|
||||
func (s *stubState) Context() context.Context { return context.Background() }
|
||||
func (s *stubState) Value() *config.Config { return s.cfg }
|
||||
func (s *stubState) Entrypoint() entrypointtypes.Entrypoint { return nil }
|
||||
func (s *stubState) ShortLinkMatcher() config.ShortLinkMatcher { return nil }
|
||||
func (s *stubState) AutoCertProvider() server.CertProvider { return nil }
|
||||
func (s *stubState) LoadOrStoreProvider(string, types.RouteProvider) (types.RouteProvider, bool) {
|
||||
return nil, false
|
||||
}
|
||||
func (s *stubState) DeleteProvider(string) { /* no-op: test stub */ }
|
||||
func (s *stubState) IterProviders() iter.Seq2[string, types.RouteProvider] {
|
||||
// no-op: returns empty iterator
|
||||
return func(func(string, types.RouteProvider) bool) {}
|
||||
}
|
||||
func (s *stubState) NumProviders() int { return 0 } // no-op: test stub
|
||||
func (s *stubState) StartProviders() error { return nil } // no-op: test stub
|
||||
func (s *stubState) FlushTmpLog() { /* no-op: test stub */ }
|
||||
func (s *stubState) StartAPIServers() { /* no-op: test stub */ }
|
||||
func (s *stubState) StartMetrics() { /* no-op: test stub */ }
|
||||
|
||||
var _ config.State = (*stubState)(nil)
|
||||
@@ -1,77 +0,0 @@
|
||||
package config
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestValidateLocalAPIAddr(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
addr string
|
||||
allowNonLoopback bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "localhost",
|
||||
addr: "localhost:8888",
|
||||
},
|
||||
{
|
||||
name: "ipv4_loopback",
|
||||
addr: "127.0.0.1:8888",
|
||||
},
|
||||
{
|
||||
name: "ipv6_loopback",
|
||||
addr: "[::1]:8888",
|
||||
},
|
||||
{
|
||||
name: "all_interfaces",
|
||||
addr: ":8888",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "all_interfaces_allowed",
|
||||
addr: ":8888",
|
||||
allowNonLoopback: true,
|
||||
},
|
||||
{
|
||||
name: "ipv4_unspecified",
|
||||
addr: "0.0.0.0:8888",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "ipv4_unspecified_allowed",
|
||||
addr: "0.0.0.0:8888",
|
||||
allowNonLoopback: true,
|
||||
},
|
||||
{
|
||||
name: "lan_ip",
|
||||
addr: "192.168.1.10:8888",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "lan_ip_allowed",
|
||||
addr: "192.168.1.10:8888",
|
||||
allowNonLoopback: true,
|
||||
},
|
||||
{
|
||||
name: "hostname_not_loopback",
|
||||
addr: "godoxy.internal:8888",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "hostname_not_loopback_allowed",
|
||||
addr: "godoxy.internal:8888",
|
||||
allowNonLoopback: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := validateLocalAPIAddr(tt.addr, tt.allowNonLoopback)
|
||||
if tt.wantErr && err == nil {
|
||||
t.Fatalf("expected error for %q", tt.addr)
|
||||
}
|
||||
if !tt.wantErr && err != nil {
|
||||
t.Fatalf("unexpected error for %q: %v", tt.addr, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,6 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"iter"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -218,15 +216,6 @@ func (state *state) StartAPIServers() {
|
||||
|
||||
// Local API Handler is used for unauthenticated access.
|
||||
if common.LocalAPIHTTPAddr != "" {
|
||||
if err := validateLocalAPIAddr(common.LocalAPIHTTPAddr, common.LocalAPIAllowNonLoopback); err != nil {
|
||||
log.Err(err).Str("addr", common.LocalAPIHTTPAddr).Msg("refusing to start local API server")
|
||||
return
|
||||
}
|
||||
if common.LocalAPIAllowNonLoopback && !isLoopbackLocalAPIHost(common.LocalAPIHTTPAddr) {
|
||||
log.Warn().
|
||||
Str("addr", common.LocalAPIHTTPAddr).
|
||||
Msg("local API server is allowed to bind to non-loopback addresses")
|
||||
}
|
||||
_, err := server.StartServer(state.task.Subtask("local_api_server", false), server.Options{
|
||||
Name: "local_api",
|
||||
HTTPAddr: common.LocalAPIHTTPAddr,
|
||||
@@ -238,51 +227,6 @@ func (state *state) StartAPIServers() {
|
||||
}
|
||||
}
|
||||
|
||||
func validateLocalAPIAddr(addr string, allowNonLoopback bool) error {
|
||||
if isLoopbackLocalAPIHost(addr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if allowNonLoopback {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch strings.ToLower(host) {
|
||||
case "localhost":
|
||||
return nil
|
||||
case "":
|
||||
return errors.New("local API address must bind to a loopback host, not all interfaces")
|
||||
}
|
||||
|
||||
ip, err := netip.ParseAddr(host)
|
||||
if err != nil {
|
||||
return fmt.Errorf("local API address must use a loopback host: %w", err)
|
||||
}
|
||||
if !ip.IsLoopback() {
|
||||
return fmt.Errorf("local API address must bind to a loopback host, got %q", host)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isLoopbackLocalAPIHost(addr string) bool {
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.EqualFold(host, "localhost") {
|
||||
return true
|
||||
}
|
||||
|
||||
ip, err := netip.ParseAddr(host)
|
||||
return err == nil && ip.IsLoopback()
|
||||
}
|
||||
|
||||
func (state *state) StartMetrics() {
|
||||
systeminfo.Poller.Start(state.task)
|
||||
uptime.Poller.Start(state.task)
|
||||
@@ -324,7 +268,6 @@ func (state *state) initEntrypoint() error {
|
||||
errs := gperr.NewBuilder("entrypoint error")
|
||||
errs.Add(state.entrypoint.SetMiddlewares(epCfg.Middlewares))
|
||||
errs.Add(state.entrypoint.SetAccessLogger(state.task, epCfg.AccessLog))
|
||||
errs.Add(state.entrypoint.SetInboundMTLSProfiles(state.Config.InboundMTLSProfiles))
|
||||
return errs.Error()
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ type (
|
||||
ACL *acl.Config `json:"acl"`
|
||||
AutoCert *autocert.Config `json:"autocert"`
|
||||
Entrypoint entrypoint.Config `json:"entrypoint"`
|
||||
InboundMTLSProfiles map[string]types.InboundMTLSProfile `json:"inbound_mtls_profiles"`
|
||||
Providers Providers `json:"providers"`
|
||||
MatchDomains []string `json:"match_domains" validate:"domain_name"`
|
||||
Homepage homepage.Config `json:"homepage"`
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
module github.com/yusing/godoxy/internal/dnsproviders
|
||||
|
||||
go 1.26.2
|
||||
go 1.26.0
|
||||
|
||||
replace github.com/yusing/godoxy => ../..
|
||||
|
||||
require (
|
||||
github.com/go-acme/lego/v4 v4.35.1
|
||||
github.com/yusing/godoxy v0.28.0
|
||||
github.com/go-acme/lego/v4 v4.32.0
|
||||
github.com/yusing/godoxy v0.26.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/auth v0.20.0 // indirect
|
||||
cloud.google.com/go/auth v0.18.2 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.1 // indirect
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v13 v13.1.0 // indirect
|
||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 // indirect
|
||||
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||
github.com/bodgit/tsig v1.2.2 // indirect
|
||||
github.com/boombuler/barcode v1.1.0 // indirect
|
||||
github.com/bytedance/gopkg v0.1.4 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.15.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.1 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
@@ -35,13 +33,13 @@ require (
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.30.2 // indirect
|
||||
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||
github.com/go-resty/resty/v2 v2.17.2 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
@@ -50,70 +48,59 @@ require (
|
||||
github.com/google/go-querystring v1.2.0 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.22.0 // indirect
|
||||
github.com/gotify/server/v2 v2.9.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.12 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
|
||||
github.com/gotify/server/v2 v2.9.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
|
||||
github.com/jcmturner/gofork v1.7.6 // indirect
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
|
||||
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
|
||||
github.com/jinzhu/copier v0.4.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/linode/linodego v1.68.0 // indirect
|
||||
github.com/linode/linodego v1.65.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.21 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/maxatome/go-testdeep v1.14.0 // indirect
|
||||
github.com/miekg/dns v1.1.72 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/nrdcg/goacmedns v0.2.0 // indirect
|
||||
github.com/nrdcg/goinwx v0.12.0 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.113.0 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.113.0 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2 // indirect
|
||||
github.com/nrdcg/porkbun v0.4.0 // indirect
|
||||
github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b // indirect
|
||||
github.com/ovh/go-ovh v1.9.0 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/pquerna/otp v1.5.0 // indirect
|
||||
github.com/puzpuzpuz/xsync/v4 v4.5.0 // indirect
|
||||
github.com/rs/zerolog v1.35.1 // indirect
|
||||
github.com/puzpuzpuz/xsync/v4 v4.4.0 // indirect
|
||||
github.com/rs/zerolog v1.34.0 // indirect
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 // indirect
|
||||
github.com/sony/gobreaker v1.0.0 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/vultr/govultr/v3 v3.31.1 // indirect
|
||||
github.com/vultr/govultr/v3 v3.27.0 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
github.com/yusing/gointernals v0.2.0 // indirect
|
||||
github.com/yusing/goutils v0.7.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect
|
||||
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||
go.uber.org/ratelimit v0.3.1 // indirect
|
||||
golang.org/x/arch v0.26.0 // indirect
|
||||
golang.org/x/crypto v0.50.0 // indirect
|
||||
golang.org/x/mod v0.35.0 // indirect
|
||||
golang.org/x/net v0.53.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
golang.org/x/tools v0.44.0 // indirect
|
||||
google.golang.org/api v0.276.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 // indirect
|
||||
google.golang.org/grpc v1.80.0 // indirect
|
||||
golang.org/x/arch v0.24.0 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/net v0.50.0 // indirect
|
||||
golang.org/x/oauth2 v0.35.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
google.golang.org/api v0.267.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
|
||||
google.golang.org/grpc v1.79.1 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/ini.v1 v1.67.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA=
|
||||
cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q=
|
||||
cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
|
||||
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 h1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1/go.mod h1:pzBXCYn05zvYIrwLgtK8Ap8QcjRg+0i76tMQdWN6wOk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw=
|
||||
@@ -25,40 +25,35 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.1 h1:edShSHV3DV90+kt+CMaEXEzR9QF7wFrPJxVGz2blMIU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.1/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v13 v13.1.0 h1:KvfpO2utLmpRq0fbC0UZRzdCERfLGLX1/dcYvG7pP7k=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v13 v13.1.0/go.mod h1:AxGyKKxAxaCNeGadscLgo+gBYEAKhNG6tRR5O0HjV30=
|
||||
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
|
||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
|
||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 h1:h/33OxYLqBk0BYmEbSUy7MlvgQR/m1w1/7OJFKoPL1I=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0/go.mod h1:rvh3imDA6EaQi+oM/GQHkQAOHbXPKJ7EWJvfjuw141Q=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
|
||||
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/bodgit/tsig v1.2.2 h1:RgxTCr8UFUHyU4D8Ygb2UtXtS4niw4B6XYYBpgCjl0k=
|
||||
github.com/bodgit/tsig v1.2.2/go.mod h1:rIGNOLZOV/UA03fmCUtEFbpWOrIoaOuETkpaeTvnLF4=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
|
||||
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
|
||||
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||
github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI=
|
||||
github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/enceve/crypto v0.0.0-20160707101852-34d48bb93815/go.mod h1:wYFFK4LYXbX7j+76mOq7aiC/EAw2S22CrzPHqgsisPw=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
@@ -67,12 +62,11 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/go-acme/lego/v4 v4.35.1 h1:xTcEBsENs4Ek5EiNpN3JXy7W5aynf5KhJ6D/HuuhXv4=
|
||||
github.com/go-acme/lego/v4 v4.35.1/go.mod h1:E+4l5mPPg9dwhTz6JJptNLvtj94E2JDObI50QuFJd3s=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-acme/lego/v4 v4.32.0 h1:z7Ss7aa1noabhKj+DBzhNCO2SM96xhE3b0ucVW3x8Tc=
|
||||
github.com/go-acme/lego/v4 v4.32.0/go.mod h1:lI2fZNdgeM/ymf9xQ9YKbgZm6MeDuf91UrohMQE4DhI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
@@ -85,14 +79,15 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ=
|
||||
github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc=
|
||||
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||
github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk=
|
||||
github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
|
||||
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
@@ -108,48 +103,20 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.15 h1:xolVQTEXusUcAA5UgtyRLjelpFFHWlPQ4XfWGc7MBas=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.15/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
||||
github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4=
|
||||
github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gotify/server/v2 v2.9.1 h1:wsQUCdYJ4ZvP7RIRKDLtAtmFQc3kxbrv3QqccO5RWzs=
|
||||
github.com/gotify/server/v2 v2.9.1/go.mod h1:8scw0hiExomp4rJDrXBwRIcgQm7kv74P4Z4B+iM4l8w=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.12 h1:Fg+zsqzYEs1ZnvmcztTYxhgCBsx3eEhEwQ1W/lHq/sQ=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.12/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
|
||||
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
|
||||
github.com/gotify/server/v2 v2.9.0 h1:2zRCl28wkq0oc6YNbyJS2n0dDOOVvOS3Oez5AG2ij54=
|
||||
github.com/gotify/server/v2 v2.9.0/go.mod h1:249wwlUqHTr0QsiKARGtFVqds0pNLIMjYLinHyMACdQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A=
|
||||
github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
|
||||
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
|
||||
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.3/go.mod h1:dqRwJGXznQrzw6cWmyo6kH+E7jksEQG/CyVWsJEsJO0=
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
|
||||
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
@@ -164,15 +131,17 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/linode/linodego v1.68.0 h1:lAsXuHm/cwQT3KCbVpMGtRiH8IpQl4hUuBOXpqkuNwo=
|
||||
github.com/linode/linodego v1.68.0/go.mod h1:X7nmTNq1GmZT4bG6w9aiuVrOnhVxYaywrzxM+buC/qU=
|
||||
github.com/linode/linodego v1.65.0 h1:SdsuGD8VSsPWeShXpE7ihl5vec+fD3MgwhnfYC/rj7k=
|
||||
github.com/linode/linodego v1.65.0/go.mod h1:tOFiTErdjkbVnV+4S0+NmIE9dqqZUEM2HsJaGu8wMh8=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
|
||||
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/maxatome/go-testdeep v1.14.0 h1:rRlLv1+kI8eOI3OaBXZwb3O7xY3exRzdW5QyX48g9wI=
|
||||
github.com/maxatome/go-testdeep v1.14.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
@@ -181,29 +150,29 @@ github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0
|
||||
github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg=
|
||||
github.com/nrdcg/goinwx v0.12.0 h1:ujdUqDBnaRSFwzVnImvPHYw3w3m9XgmGImNUw1GyMb4=
|
||||
github.com/nrdcg/goinwx v0.12.0/go.mod h1:IrVKd3ZDbFiMjdPgML4CSxZAY9wOoqLvH44zv3NodJ0=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.113.0 h1:OLlJVGHkTHBCXdTGpNn5ay4DV3gOZrVLxlUM6xBQrIM=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.113.0/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.113.0 h1:bveOZN6gZZjjEM1T9o1TUm8de3zePyDbfuluMpaRJCE=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.113.0/go.mod h1:Ff6Cxm43tuGJHyltQwD3EXLs7kKpkxzqy6DGQcaiS/0=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2 h1:OWijzl3nHUApvTivl+3+78dbBwmyEHOnb+W9m6ixGbk=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2 h1:9LsjN/zaIN7H8JE61NHpbWhxF0UGY96+kMlk3g8OvGU=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2/go.mod h1:32vZH06TuwZSn+IDMO1qcDvC2vHVlzUALCwXGWPA+dc=
|
||||
github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw=
|
||||
github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54=
|
||||
github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b h1:it0YPE/evO6/m8t8wxis9KFI2F/aleOKsI6d9uz0cEk=
|
||||
github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b/go.mod h1:tNrEB5k8SI+g5kOlsCmL2ELASfpqEofI0+FLBgBdN08=
|
||||
github.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE=
|
||||
github.com/ovh/go-ovh v1.9.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.5.0 h1:vOSWu6b57/emh+L/Cw0BeQfvxa/cogFywXHeGUxQxAg=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.5.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.4.0 h1:vlSN6/CkEY0pY8KaB0yqo/pCLZvp9nhdbBdjipT4gWo=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.4.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI=
|
||||
github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 h1:ObX9hZmK+VmijreZO/8x9pQ8/P/ToHD/bdSb4Eg4tUo=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36/go.mod h1:LEsDu4BubxK7/cWhtlQWfuxwL4rf/2UEpxXz1o1EMtM=
|
||||
github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
|
||||
@@ -218,123 +187,78 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/vultr/govultr/v3 v3.31.1 h1:AoJRZ0WDS1J1otp2wk3OD4aYh4EQxFU+kvyOPdCe4+Y=
|
||||
github.com/vultr/govultr/v3 v3.31.1/go.mod h1:2zyUw9yADQaGwKnwDesmIOlBNLrm7edsCfWHFJpWKf8=
|
||||
github.com/vultr/govultr/v3 v3.27.0 h1:J8etMyu/Jh5+idMsu2YZpOWmDXXHeW4VZnkYXmJYHx8=
|
||||
github.com/vultr/govultr/v3 v3.27.0/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusing/gointernals v0.2.0 h1:jyWB3kdUPkuU6s0r8QY/sS5h2WNBF4Kfisly8dtSVvg=
|
||||
github.com/yusing/gointernals v0.2.0/go.mod h1:xGzNbPGMm5Z8kG0t4JYISMscw+gMQlgghkLxlgRZv5Y=
|
||||
github.com/yusing/goutils v0.7.0 h1:I5hd8GwZ+3WZqFPK0tWqek1Q5MY6Xg29hKZcwwQi4SY=
|
||||
github.com/yusing/goutils v0.7.0/go.mod h1:CtF/KFH4q8jkr7cvBpkaExnudE0lLu8sLe43F73Bn5Q=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
|
||||
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
|
||||
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0=
|
||||
go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk=
|
||||
golang.org/x/arch v0.26.0 h1:jZ6dpec5haP/fUv1kLCbuJy6dnRrfX6iVK08lZBFpk4=
|
||||
golang.org/x/arch v0.26.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/mod v0.4.2/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.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/arch v0.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y=
|
||||
golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
||||
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/api v0.276.0 h1:nVArUtfLEihtW+b0DdcqRGK1xoEm2+ltAihyztq7MKY=
|
||||
google.golang.org/api v0.276.0/go.mod h1:Fnag/EWUPIcJXuIkP1pjoTgS5vdxlk3eeemL7Do6bvw=
|
||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
|
||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 h1:XF8+t6QQiS0o9ArVan/HW8Q7cycNPGsJf6GA2nXxYAg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/api v0.267.0 h1:w+vfWPMPYeRs8qH1aYYsFX68jMls5acWl/jocfLomwE=
|
||||
google.golang.org/api v0.267.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0=
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -18,19 +15,13 @@ var ErrInvalidLabel = errors.New("invalid label")
|
||||
|
||||
const nsProxyDot = NSProxy + "."
|
||||
|
||||
type UnexpectedTypeError struct {
|
||||
Expected string
|
||||
Actual any
|
||||
// Message, if non-empty, is returned by Error() instead of the default "expect …, got …" form.
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e UnexpectedTypeError) Error() string {
|
||||
if e.Message != "" {
|
||||
return e.Message
|
||||
}
|
||||
return fmt.Sprintf("expect %s, got %T", e.Expected, e.Actual)
|
||||
var refPrefixes = func() []string {
|
||||
prefixes := make([]string, 100)
|
||||
for i := range prefixes {
|
||||
prefixes[i] = nsProxyDot + "#" + strconv.Itoa(i+1) + "."
|
||||
}
|
||||
return prefixes
|
||||
}()
|
||||
|
||||
func ParseLabels(labels map[string]string, aliases ...string) (types.LabelMap, error) {
|
||||
nestedMap := make(types.LabelMap)
|
||||
@@ -38,125 +29,44 @@ func ParseLabels(labels map[string]string, aliases ...string) (types.LabelMap, e
|
||||
|
||||
ExpandWildcard(labels, aliases...)
|
||||
|
||||
keys := slices.SortedFunc(maps.Keys(labels), compareLabelKeys)
|
||||
for _, lbl := range keys {
|
||||
if err := applyLabel(nestedMap, lbl, labels[lbl]); err != nil {
|
||||
errs.AddSubject(err, lbl)
|
||||
for lbl, value := range labels {
|
||||
parts := strings.Split(lbl, ".")
|
||||
if parts[0] != NSProxy {
|
||||
continue
|
||||
}
|
||||
if len(parts) == 1 {
|
||||
errs.AddSubject(ErrInvalidLabel, lbl)
|
||||
continue
|
||||
}
|
||||
parts = parts[1:]
|
||||
currentMap := nestedMap
|
||||
|
||||
for i, k := range parts {
|
||||
if i == len(parts)-1 {
|
||||
// Last element, set the value
|
||||
currentMap[k] = value
|
||||
} else {
|
||||
// If the key doesn't exist, create a new map
|
||||
if _, exists := currentMap[k]; !exists {
|
||||
currentMap[k] = make(types.LabelMap)
|
||||
}
|
||||
// Move deeper into the nested map
|
||||
m, ok := currentMap[k].(types.LabelMap)
|
||||
if !ok && currentMap[k] != "" {
|
||||
errs.AddSubject(fmt.Errorf("expect mapping, got %T", currentMap[k]), lbl)
|
||||
continue
|
||||
} else if !ok {
|
||||
m = make(types.LabelMap)
|
||||
currentMap[k] = m
|
||||
}
|
||||
currentMap = m
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nestedMap, errs.Error()
|
||||
}
|
||||
|
||||
func applyLabel(dst types.LabelMap, lbl, value string) error {
|
||||
parts := strings.Split(lbl, ".")
|
||||
if parts[0] != NSProxy {
|
||||
return nil
|
||||
}
|
||||
if len(parts) == 1 {
|
||||
return ErrInvalidLabel
|
||||
}
|
||||
|
||||
currentMap := dst
|
||||
for _, part := range parts[1 : len(parts)-1] {
|
||||
nextMap, err := descendLabelMap(currentMap, part)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentMap = nextMap
|
||||
}
|
||||
|
||||
return setLabelValue(currentMap, parts[len(parts)-1], value)
|
||||
}
|
||||
|
||||
func descendLabelMap(currentMap types.LabelMap, key string) (types.LabelMap, error) {
|
||||
if next, ok := currentMap[key]; ok {
|
||||
switch typed := next.(type) {
|
||||
case types.LabelMap:
|
||||
return typed, nil
|
||||
case string:
|
||||
objectValue, isObject := parseLabelObject(typed)
|
||||
if !isObject {
|
||||
return nil, UnexpectedTypeError{Expected: "mapping", Actual: next}
|
||||
}
|
||||
currentMap[key] = objectValue
|
||||
return objectValue, nil
|
||||
default:
|
||||
return nil, UnexpectedTypeError{Expected: "mapping", Actual: next}
|
||||
}
|
||||
}
|
||||
|
||||
nextMap := make(types.LabelMap)
|
||||
currentMap[key] = nextMap
|
||||
return nextMap, nil
|
||||
}
|
||||
|
||||
func setLabelValue(currentMap types.LabelMap, key, value string) error {
|
||||
existing, ok := currentMap[key].(types.LabelMap)
|
||||
if !ok {
|
||||
currentMap[key] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
objectValue, isObject := parseLabelObject(value)
|
||||
if !isObject {
|
||||
return UnexpectedTypeError{Expected: "mapping", Actual: value}
|
||||
}
|
||||
return mergeLabelMaps(existing, objectValue)
|
||||
}
|
||||
|
||||
func parseLabelObject(value string) (types.LabelMap, bool) {
|
||||
if value == "" {
|
||||
return make(types.LabelMap), true
|
||||
}
|
||||
|
||||
objectValue := make(types.LabelMap)
|
||||
if err := yaml.Unmarshal([]byte(strings.ReplaceAll(value, "\t", " ")), &objectValue); err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return objectValue, true
|
||||
}
|
||||
|
||||
func mergeLabelMaps(dst, src types.LabelMap) error {
|
||||
for key, srcValue := range src {
|
||||
existingValue, exists := dst[key]
|
||||
if !exists {
|
||||
dst[key] = srcValue
|
||||
continue
|
||||
}
|
||||
|
||||
existingMap, existingIsMap := existingValue.(types.LabelMap)
|
||||
srcMap, srcIsMap := srcValue.(types.LabelMap)
|
||||
if existingIsMap && srcIsMap {
|
||||
if err := mergeLabelMaps(existingMap, srcMap); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if existingIsMap {
|
||||
return UnexpectedTypeError{Expected: "mapping", Actual: srcValue}
|
||||
}
|
||||
if srcIsMap {
|
||||
return UnexpectedTypeError{
|
||||
Expected: "scalar",
|
||||
Actual: srcValue,
|
||||
Message: fmt.Sprintf(
|
||||
"cannot merge mapping into existing scalar; merge source is %T",
|
||||
srcValue,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func compareLabelKeys(a, b string) int {
|
||||
if parts := cmp.Compare(strings.Count(a, "."), strings.Count(b, ".")); parts != 0 {
|
||||
return parts
|
||||
}
|
||||
return cmp.Compare(a, b)
|
||||
}
|
||||
|
||||
func ExpandWildcard(labels map[string]string, aliases ...string) {
|
||||
aliasSet := make(map[string]int, len(aliases))
|
||||
for i, alias := range aliases {
|
||||
@@ -167,10 +77,12 @@ func ExpandWildcard(labels map[string]string, aliases ...string) {
|
||||
|
||||
// First pass: collect wildcards and discover aliases
|
||||
for lbl, value := range labels {
|
||||
alias, suffix, ok := splitAliasLabel(lbl)
|
||||
if !ok {
|
||||
if !strings.HasPrefix(lbl, nsProxyDot) {
|
||||
continue
|
||||
}
|
||||
// lbl is "proxy.X..." where X is alias or wildcard
|
||||
rest := lbl[len(nsProxyDot):] // "X..." or "X.suffix"
|
||||
alias, suffix, _ := strings.Cut(rest, ".")
|
||||
if alias == WildcardAlias {
|
||||
delete(labels, lbl)
|
||||
if suffix == "" || strings.Count(value, "\n") > 1 {
|
||||
@@ -196,10 +108,15 @@ func ExpandWildcard(labels map[string]string, aliases ...string) {
|
||||
|
||||
// Second pass: convert explicit labels to #N format
|
||||
for lbl, value := range labels {
|
||||
alias, suffix, ok := splitAliasLabel(lbl)
|
||||
if !ok || suffix == "" || alias == "" || alias[0] == '#' {
|
||||
if !strings.HasPrefix(lbl, nsProxyDot) {
|
||||
continue
|
||||
}
|
||||
rest := lbl[len(nsProxyDot):]
|
||||
alias, suffix, ok := strings.Cut(rest, ".")
|
||||
if !ok || alias == "" || alias[0] == '#' {
|
||||
continue
|
||||
}
|
||||
|
||||
idx, known := aliasSet[alias]
|
||||
if !known {
|
||||
continue
|
||||
@@ -207,27 +124,18 @@ func ExpandWildcard(labels map[string]string, aliases ...string) {
|
||||
|
||||
delete(labels, lbl)
|
||||
if _, overridden := wildcardLabels[suffix]; !overridden {
|
||||
labels[refPrefix(idx)+suffix] = value
|
||||
labels[refPrefixes[idx]+suffix] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Expand wildcards for all aliases
|
||||
for suffix, value := range wildcardLabels {
|
||||
for _, idx := range aliasSet {
|
||||
labels[refPrefix(idx)+suffix] = value
|
||||
labels[refPrefixes[idx]+suffix] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func splitAliasLabel(lbl string) (alias, suffix string, ok bool) {
|
||||
rest, ok := strings.CutPrefix(lbl, nsProxyDot)
|
||||
if !ok {
|
||||
return "", "", false
|
||||
}
|
||||
alias, suffix, _ = strings.Cut(rest, ".")
|
||||
return alias, suffix, true
|
||||
}
|
||||
|
||||
// expandYamlWildcard parses a YAML document in value, flattens it to dot-notated keys and adds the
|
||||
// results into dest map where each key is the flattened suffix and the value is the scalar string
|
||||
// representation. The provided YAML is expected to be a mapping.
|
||||
@@ -244,53 +152,59 @@ func expandYamlWildcard(value string, dest map[string]string) {
|
||||
flattenMap("", raw, dest)
|
||||
}
|
||||
|
||||
// refPrefix returns the prefix for a reference to the Nth alias.
|
||||
func refPrefix(n int) string {
|
||||
return nsProxyDot + "#" + strconv.Itoa(n+1) + "."
|
||||
}
|
||||
|
||||
// flattenMap converts nested maps into a flat map with dot-delimited keys.
|
||||
func flattenMap(prefix string, src map[string]any, dest map[string]string) {
|
||||
for k, v := range src {
|
||||
flattenValue(joinLabelKey(prefix, k), v, dest)
|
||||
key := k
|
||||
if prefix != "" {
|
||||
key = prefix + "." + k
|
||||
}
|
||||
switch vv := v.(type) {
|
||||
case map[string]any:
|
||||
flattenMap(key, vv, dest)
|
||||
case map[any]any:
|
||||
flattenMapAny(key, vv, dest)
|
||||
case string:
|
||||
dest[key] = vv
|
||||
case int:
|
||||
dest[key] = strconv.Itoa(vv)
|
||||
case bool:
|
||||
dest[key] = strconv.FormatBool(vv)
|
||||
case float64:
|
||||
dest[key] = strconv.FormatFloat(vv, 'f', -1, 64)
|
||||
default:
|
||||
dest[key] = fmt.Sprint(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func flattenMapAny(prefix string, src map[any]any, dest map[string]string) {
|
||||
for k, v := range src {
|
||||
flattenValue(joinLabelKey(prefix, stringifyLabelKey(k)), v, dest)
|
||||
}
|
||||
}
|
||||
|
||||
func flattenValue(key string, value any, dest map[string]string) {
|
||||
switch typed := value.(type) {
|
||||
case map[string]any:
|
||||
flattenMap(key, typed, dest)
|
||||
case map[any]any:
|
||||
flattenMapAny(key, typed, dest)
|
||||
var key string
|
||||
switch kk := k.(type) {
|
||||
case string:
|
||||
dest[key] = typed
|
||||
case int:
|
||||
dest[key] = strconv.Itoa(typed)
|
||||
case bool:
|
||||
dest[key] = strconv.FormatBool(typed)
|
||||
case float64:
|
||||
dest[key] = strconv.FormatFloat(typed, 'f', -1, 64)
|
||||
key = kk
|
||||
default:
|
||||
dest[key] = fmt.Sprint(value)
|
||||
key = fmt.Sprint(k)
|
||||
}
|
||||
if prefix != "" {
|
||||
key = prefix + "." + key
|
||||
}
|
||||
switch vv := v.(type) {
|
||||
case map[string]any:
|
||||
flattenMap(key, vv, dest)
|
||||
case map[any]any:
|
||||
flattenMapAny(key, vv, dest)
|
||||
case string:
|
||||
dest[key] = vv
|
||||
case int:
|
||||
dest[key] = strconv.Itoa(vv)
|
||||
case bool:
|
||||
dest[key] = strconv.FormatBool(vv)
|
||||
case float64:
|
||||
dest[key] = strconv.FormatFloat(vv, 'f', -1, 64)
|
||||
default:
|
||||
dest[key] = fmt.Sprint(v)
|
||||
}
|
||||
}
|
||||
|
||||
func joinLabelKey(prefix, key string) string {
|
||||
if prefix == "" {
|
||||
return key
|
||||
}
|
||||
return prefix + "." + key
|
||||
}
|
||||
|
||||
func stringifyLabelKey(key any) string {
|
||||
if typed, ok := key.(string); ok {
|
||||
return typed
|
||||
}
|
||||
return fmt.Sprint(key)
|
||||
}
|
||||
|
||||
@@ -1,310 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
)
|
||||
|
||||
func TestParseLabelsIgnoresNonProxyAndRejectsInvalidRoot(t *testing.T) {
|
||||
parsed, err := ParseLabels(map[string]string{
|
||||
"other.label": "value",
|
||||
"proxy": "invalid",
|
||||
})
|
||||
|
||||
require.ErrorIs(t, err, ErrInvalidLabel)
|
||||
require.Empty(t, parsed)
|
||||
}
|
||||
|
||||
func TestParseLabelsPromotesEmptyStringIntoNestedObject(t *testing.T) {
|
||||
parsed, err := ParseLabels(map[string]string{
|
||||
"proxy.a.b": "",
|
||||
"proxy.a.b.c": "value",
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, types.LabelMap{
|
||||
"a": types.LabelMap{
|
||||
"b": types.LabelMap{
|
||||
"c": "value",
|
||||
},
|
||||
},
|
||||
}, parsed)
|
||||
}
|
||||
|
||||
func TestParseLabelsMergesObjectIntoExistingMap(t *testing.T) {
|
||||
parsed, err := ParseLabels(map[string]string{
|
||||
"proxy.a.b": "c: generic\nd: merged",
|
||||
"proxy.a.b.c": "specific",
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, types.LabelMap{
|
||||
"a": types.LabelMap{
|
||||
"b": types.LabelMap{
|
||||
"c": "specific",
|
||||
"d": "merged",
|
||||
},
|
||||
},
|
||||
}, parsed)
|
||||
}
|
||||
|
||||
func TestParseLabelsRejectsInvalidObjectMergeValue(t *testing.T) {
|
||||
parsed, err := ParseLabels(map[string]string{
|
||||
"proxy.a.b": "- invalid",
|
||||
"proxy.a.b.c": "specific",
|
||||
})
|
||||
|
||||
require.ErrorContains(t, err, "proxy.a.b.c")
|
||||
require.ErrorContains(t, err, "expect mapping, got string")
|
||||
require.Equal(t, types.LabelMap{
|
||||
"a": types.LabelMap{
|
||||
"b": "- invalid",
|
||||
},
|
||||
}, parsed)
|
||||
}
|
||||
|
||||
func TestParseLabelsRejectsSpecificFieldOverrideOfNestedObjectField(t *testing.T) {
|
||||
parsed, err := ParseLabels(map[string]string{
|
||||
"proxy.a.b": "c:\n nested: value",
|
||||
"proxy.a.b.c": "specific",
|
||||
})
|
||||
|
||||
require.ErrorContains(t, err, "proxy.a.b.c")
|
||||
require.ErrorContains(t, err, "expect mapping, got string")
|
||||
require.Equal(t, types.LabelMap{
|
||||
"a": types.LabelMap{
|
||||
"b": types.LabelMap{
|
||||
"c": types.LabelMap{
|
||||
"nested": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, parsed)
|
||||
}
|
||||
|
||||
func TestParseLabelsMergesIntoExistingNestedMap(t *testing.T) {
|
||||
parsed, err := ParseLabels(map[string]string{
|
||||
"proxy.a.b": "c:\n nested:\n allow: true",
|
||||
"proxy.a.b.c": "nested:\n deny: true",
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, types.LabelMap{
|
||||
"a": types.LabelMap{
|
||||
"b": types.LabelMap{
|
||||
"c": types.LabelMap{
|
||||
"nested": types.LabelMap{
|
||||
"allow": true,
|
||||
"deny": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, parsed)
|
||||
}
|
||||
|
||||
func TestParseLabelsRejectsInvalidNestedObjectMergeValue(t *testing.T) {
|
||||
parsed, err := ParseLabels(map[string]string{
|
||||
"proxy.a.b": "c:\n nested: value",
|
||||
"proxy.a.b.c": "- invalid",
|
||||
})
|
||||
|
||||
require.ErrorContains(t, err, "proxy.a.b.c")
|
||||
require.ErrorContains(t, err, "expect mapping, got string")
|
||||
require.Equal(t, types.LabelMap{
|
||||
"a": types.LabelMap{
|
||||
"b": types.LabelMap{
|
||||
"c": types.LabelMap{
|
||||
"nested": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, parsed)
|
||||
}
|
||||
|
||||
func TestParseLabelsRejectsConflictingNestedObjectMerge(t *testing.T) {
|
||||
parsed, err := ParseLabels(map[string]string{
|
||||
"proxy.a.b": "c:\n nested:\n allow: true",
|
||||
"proxy.a.b.c": "nested: blocked",
|
||||
})
|
||||
|
||||
require.ErrorContains(t, err, "proxy.a.b.c")
|
||||
require.ErrorContains(t, err, "expect mapping, got string")
|
||||
require.Equal(t, types.LabelMap{
|
||||
"a": types.LabelMap{
|
||||
"b": types.LabelMap{
|
||||
"c": types.LabelMap{
|
||||
"nested": types.LabelMap{
|
||||
"allow": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, parsed)
|
||||
}
|
||||
|
||||
func TestParseLabelsRejectsNestedFieldInsideScalarObjectMember(t *testing.T) {
|
||||
parsed, err := ParseLabels(map[string]string{
|
||||
"proxy.a.b": "c: 1",
|
||||
"proxy.a.b.c.d": "value",
|
||||
})
|
||||
|
||||
require.ErrorContains(t, err, "proxy.a.b.c.d")
|
||||
require.ErrorContains(t, err, "expect mapping, got uint64")
|
||||
require.Equal(t, types.LabelMap{
|
||||
"a": types.LabelMap{
|
||||
"b": types.LabelMap{
|
||||
"c": uint64(1),
|
||||
},
|
||||
},
|
||||
}, parsed)
|
||||
}
|
||||
|
||||
func TestParseLabelObject(t *testing.T) {
|
||||
t.Run("empty string becomes empty map", func(t *testing.T) {
|
||||
parsed, ok := parseLabelObject("")
|
||||
require.True(t, ok)
|
||||
require.Empty(t, parsed)
|
||||
})
|
||||
|
||||
t.Run("yaml object parses", func(t *testing.T) {
|
||||
parsed, ok := parseLabelObject("nested:\n\tvalue: true")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, types.LabelMap{
|
||||
"nested": types.LabelMap{
|
||||
"value": true,
|
||||
},
|
||||
}, parsed)
|
||||
})
|
||||
|
||||
t.Run("non-object yaml is rejected", func(t *testing.T) {
|
||||
parsed, ok := parseLabelObject("- item")
|
||||
require.False(t, ok)
|
||||
require.Nil(t, parsed)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMergeLabelMaps(t *testing.T) {
|
||||
t.Run("recursively merges nested maps and preserves specific scalar overrides", func(t *testing.T) {
|
||||
dst := types.LabelMap{
|
||||
"allowed_groups": []any{"specific"},
|
||||
"bypass": types.LabelMap{
|
||||
"path": "/private",
|
||||
},
|
||||
}
|
||||
src := types.LabelMap{
|
||||
"allowed_groups": []any{"generic"},
|
||||
"bypass": types.LabelMap{
|
||||
"methods": "GET",
|
||||
},
|
||||
"priority": 5,
|
||||
}
|
||||
|
||||
err := mergeLabelMaps(dst, src)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, types.LabelMap{
|
||||
"allowed_groups": []any{"specific"},
|
||||
"bypass": types.LabelMap{
|
||||
"path": "/private",
|
||||
"methods": "GET",
|
||||
},
|
||||
"priority": 5,
|
||||
}, dst)
|
||||
})
|
||||
|
||||
t.Run("rejects map receiving scalar", func(t *testing.T) {
|
||||
err := mergeLabelMaps(types.LabelMap{
|
||||
"bypass": types.LabelMap{"path": "/private"},
|
||||
}, types.LabelMap{
|
||||
"bypass": "skip",
|
||||
})
|
||||
|
||||
require.ErrorContains(t, err, "expect mapping")
|
||||
})
|
||||
|
||||
t.Run("rejects scalar receiving map", func(t *testing.T) {
|
||||
err := mergeLabelMaps(types.LabelMap{
|
||||
"bypass": "skip",
|
||||
}, types.LabelMap{
|
||||
"bypass": types.LabelMap{"path": "/private"},
|
||||
})
|
||||
|
||||
require.ErrorContains(t, err, "cannot merge mapping into existing scalar")
|
||||
})
|
||||
|
||||
t.Run("rejects nested recursive map conflicts", func(t *testing.T) {
|
||||
err := mergeLabelMaps(types.LabelMap{
|
||||
"outer": types.LabelMap{
|
||||
"nested": types.LabelMap{"allow": true},
|
||||
},
|
||||
}, types.LabelMap{
|
||||
"outer": types.LabelMap{
|
||||
"nested": "blocked",
|
||||
},
|
||||
})
|
||||
|
||||
require.ErrorContains(t, err, "expect mapping")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCompareLabelKeys(t *testing.T) {
|
||||
require.Less(t, compareLabelKeys("proxy.a", "proxy.a.b"), 0)
|
||||
require.Less(t, compareLabelKeys("proxy.a.a", "proxy.a.b"), 0)
|
||||
require.Greater(t, compareLabelKeys("proxy.a.c", "proxy.a.b"), 0)
|
||||
}
|
||||
|
||||
func TestFlattenMapAny(t *testing.T) {
|
||||
dest := make(map[string]string)
|
||||
|
||||
flattenMapAny("", map[any]any{
|
||||
"nested": map[any]any{
|
||||
"string": "value",
|
||||
"int": 7,
|
||||
"bool": true,
|
||||
"float": 1.5,
|
||||
9: "numeric-key",
|
||||
"map": map[string]any{
|
||||
"child": "value",
|
||||
},
|
||||
},
|
||||
"list": []int{1, 2},
|
||||
}, dest)
|
||||
|
||||
require.Equal(t, map[string]string{
|
||||
"nested.string": "value",
|
||||
"nested.int": "7",
|
||||
"nested.bool": "true",
|
||||
"nested.float": "1.5",
|
||||
"nested.9": "numeric-key",
|
||||
"nested.map.child": "value",
|
||||
"list": "[1 2]",
|
||||
}, dest)
|
||||
}
|
||||
|
||||
func TestFlattenMap(t *testing.T) {
|
||||
dest := make(map[string]string)
|
||||
|
||||
flattenMap("", map[string]any{
|
||||
"nested": map[string]any{
|
||||
"string": "value",
|
||||
"mapany": map[any]any{
|
||||
"child": "nested-value",
|
||||
},
|
||||
"int": 7,
|
||||
"bool": true,
|
||||
"float": 1.5,
|
||||
},
|
||||
"list": []int{1, 2},
|
||||
}, dest)
|
||||
|
||||
require.Equal(t, map[string]string{
|
||||
"nested.string": "value",
|
||||
"nested.mapany.child": "nested-value",
|
||||
"nested.int": "7",
|
||||
"nested.bool": "true",
|
||||
"nested.float": "1.5",
|
||||
"list": "[1 2]",
|
||||
}, dest)
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package docker_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -243,14 +242,6 @@ port: 8080`[1:]
|
||||
})
|
||||
}
|
||||
|
||||
func requireMap(t *testing.T, value any) map[string]any {
|
||||
t.Helper()
|
||||
|
||||
m, ok := value.(map[string]any)
|
||||
require.True(t, ok, "expected map[string]any, got %T", value)
|
||||
return m
|
||||
}
|
||||
|
||||
func BenchmarkParseLabels(b *testing.B) {
|
||||
m := map[string]string{
|
||||
"proxy.a.host": "localhost",
|
||||
@@ -262,39 +253,3 @@ func BenchmarkParseLabels(b *testing.B) {
|
||||
_, _ = docker.ParseLabels(m, "a", "b")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseLabelsMixedObjectAndFlatFields(t *testing.T) {
|
||||
for i := range 100 {
|
||||
labels := map[string]string{
|
||||
"proxy.universal.middlewares.oidc": "allowed_groups: [everyone]",
|
||||
"proxy.universal.middlewares.oidc.bypass": "- path glob(/geheimenvan/*)",
|
||||
}
|
||||
|
||||
parsed, err := docker.ParseLabels(labels)
|
||||
require.NoError(t, err, fmt.Sprintf("iteration %d", i))
|
||||
|
||||
universal := requireMap(t, parsed["universal"])
|
||||
middlewares := requireMap(t, universal["middlewares"])
|
||||
oidc := requireMap(t, middlewares["oidc"])
|
||||
|
||||
require.Equal(t, []any{"everyone"}, oidc["allowed_groups"])
|
||||
require.Equal(t, "- path glob(/geheimenvan/*)", oidc["bypass"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseLabelsRejectsScalarAndNestedObjectConflict(t *testing.T) {
|
||||
for i := range 100 {
|
||||
parsed, err := docker.ParseLabels(map[string]string{
|
||||
"proxy.universal.middlewares.oidc": "bypass: skip",
|
||||
"proxy.universal.middlewares.oidc.bypass.path": "/geheimenvan",
|
||||
})
|
||||
|
||||
require.ErrorContains(t, err, "proxy.universal.middlewares.oidc.bypass.path")
|
||||
require.ErrorContains(t, err, "expect mapping, got string")
|
||||
|
||||
universal := requireMap(t, parsed["universal"])
|
||||
middlewares := requireMap(t, universal["middlewares"])
|
||||
oidc := requireMap(t, middlewares["oidc"])
|
||||
require.Equal(t, "skip", oidc["bypass"], "iteration %d", i)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ The entrypoint package implements the primary HTTP handler that receives all inc
|
||||
- Domain-based route lookup with subdomain support
|
||||
- Short link (`go/<alias>` domain) handling
|
||||
- Middleware chain application
|
||||
- Route-specific promotion of middleware overlays into matching entrypoint middleware
|
||||
- Access logging for all requests
|
||||
- Configurable not-found handling
|
||||
- Per-domain route resolution
|
||||
@@ -94,7 +93,6 @@ type RWPoolLike[Route types.Route] interface {
|
||||
```go
|
||||
type Config struct {
|
||||
SupportProxyProtocol bool `json:"support_proxy_protocol"`
|
||||
InboundMTLSProfile string `json:"inbound_mtls_profile,omitempty"`
|
||||
Rules struct {
|
||||
NotFound rules.Rules `json:"not_found"`
|
||||
} `json:"rules"`
|
||||
@@ -103,90 +101,6 @@ type Config struct {
|
||||
}
|
||||
```
|
||||
|
||||
### Entrypoint middleware bypass overlays
|
||||
|
||||
For HTTP routes, the entrypoint can compile a route-specific effective middleware chain when a route contributes a local middleware entry whose name matches an existing entrypoint middleware and whose options include `bypass`.
|
||||
|
||||
Behavior is intentionally narrow:
|
||||
|
||||
- only `bypass` is promoted in v1
|
||||
- promotion is **append-only**
|
||||
- the entrypoint middleware must already exist
|
||||
- if no matching entrypoint middleware exists, route-local behavior stays unchanged
|
||||
- when the route-local middleware entry is bypass-only, it is consumed after promotion so the same middleware is not evaluated twice
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
entrypoint:
|
||||
middlewares:
|
||||
- use: oidc
|
||||
|
||||
routes:
|
||||
app:
|
||||
middlewares:
|
||||
oidc:
|
||||
bypass:
|
||||
- path glob("/public/*")
|
||||
```
|
||||
|
||||
This behaves as if the entrypoint middleware for that route had:
|
||||
|
||||
```yaml
|
||||
entrypoint:
|
||||
middlewares:
|
||||
- use: oidc
|
||||
bypass:
|
||||
- route app & path glob("/public/*")
|
||||
```
|
||||
|
||||
Pre-existing entrypoint bypass rules remain active; route bypass rules are added on top.
|
||||
|
||||
`InboundMTLSProfile` references a named root-level inbound mTLS profile and enables Go's built-in client-certificate verification (`tls.RequireAndVerifyClientCert`) for all HTTPS traffic on the entrypoint.
|
||||
|
||||
- When configured, route-level inbound mTLS overrides are not supported.
|
||||
- Without a global profile, route-level inbound mTLS may still select profiles by TLS SNI.
|
||||
- For a route that enforces client certificates, the route matched from the HTTP `Host` and the route matched from TLS SNI must be the same route (compared by route identity/key after `FindRoute`). That resolution is the entrypoint's route table, not DNS or any external name resolution.
|
||||
|
||||
### Inbound mTLS profiles
|
||||
|
||||
Root config provides reusable named inbound mTLS profiles via `config.Config.InboundMTLSProfiles`. Each profile is a [`types.InboundMTLSProfile`](internal/types/inbound_mtls.go): optional system trust roots plus zero or more PEM CA certificate files on disk (`ca_files`). `SetInboundMTLSProfiles` compiles those profiles into certificate pools, and the TLS server sets `ClientCAs` from the selected pool.
|
||||
|
||||
PEM content is not embedded in YAML: list file paths under `ca_files`; each file should contain one or more PEM-encoded CA certificates.
|
||||
|
||||
```yaml
|
||||
inbound_mtls_profiles:
|
||||
corp-clients:
|
||||
use_system_cas: false
|
||||
ca_files:
|
||||
- /etc/godoxy/mtls/corp-root-ca.pem
|
||||
- /etc/godoxy/mtls/corp-issuing-ca.pem
|
||||
corp-plus-extra:
|
||||
use_system_cas: true
|
||||
ca_files:
|
||||
- /etc/godoxy/mtls/private-intermediate.pem
|
||||
```
|
||||
|
||||
Apply one profile to **all** HTTPS listeners by naming it on the entrypoint:
|
||||
|
||||
```yaml
|
||||
entrypoint:
|
||||
inbound_mtls_profile: corp-clients
|
||||
```
|
||||
|
||||
#### Security considerations
|
||||
|
||||
- **Client certificates and chain verification** — The server requires a client certificate and verifies it with Go's TLS stack. The chain must build to one of the CAs in the selected pool (custom PEMs from `ca_files`, and optionally the OS trust store when `use_system_cas` is true). Leaf validity (time, EKU, and related checks) follows standard Go behavior for client-auth verification.
|
||||
- **CA management and rotation** — CA material is read from the filesystem when profiles are compiled during config load / entrypoint setup. Updating trust for a running process requires a config reload or restart so the new PEM files are read.
|
||||
- **CRL / OCSP revocation** — Go's standard inbound mTLS verification does not perform CRL or OCSP checks for client certificates, and GoDoxy does not add a custom revocation layer.
|
||||
- **Misconfigured trust pools** — A pool that is too broad (for example `use_system_cas: true` with few constraints) can trust far more clients than intended. A pool that omits required intermediates can reject otherwise valid clients.
|
||||
|
||||
#### Failure modes
|
||||
|
||||
- **Invalid or unreadable CA material** — Missing files, non-PEM content, or PEM that does not parse as CA certificates cause profile compilation to fail. `SetInboundMTLSProfiles` returns collected per-profile errors.
|
||||
- **Missing profile referenced by entrypoint** — If `entrypoint.inbound_mtls_profile` names a profile that is not present in `inbound_mtls_profiles`, initialization returns `entrypoint inbound mTLS profile "<name>" not found`.
|
||||
- **Client certificate validation failures** — Clients that omit a cert, present a cert that does not chain to the configured pool, or fail other TLS checks see a failed TLS handshake before HTTP handling starts.
|
||||
|
||||
### Context Functions
|
||||
|
||||
```go
|
||||
@@ -208,9 +122,9 @@ classDiagram
|
||||
+accessLogger AccessLogger
|
||||
+findRouteFunc findRouteFunc
|
||||
+shortLinkMatcher *ShortLinkMatcher
|
||||
+streamRoutes *pool.Pool\[types.StreamRoute\]
|
||||
+excludedRoutes *pool.Pool\[types.Route\]
|
||||
+servers *xsync.Map\[string, *httpServer\]
|
||||
+streamRoutes *pool.Pool[types.StreamRoute]
|
||||
+excludedRoutes *pool.Pool[types.Route]
|
||||
+servers *xsync.Map[string, *httpServer]
|
||||
+SupportProxyProtocol() bool
|
||||
+StartAddRoute(r) error
|
||||
+IterRoutes(yield)
|
||||
@@ -218,7 +132,7 @@ classDiagram
|
||||
}
|
||||
|
||||
class httpServer {
|
||||
+routes *pool.Pool\[types.HTTPRoute\]
|
||||
+routes *pool.Pool[types.HTTPRoute]
|
||||
+ServeHTTP(w, r)
|
||||
+AddRoute(route)
|
||||
+DelRoute(route)
|
||||
@@ -240,8 +154,8 @@ classDiagram
|
||||
}
|
||||
|
||||
class ShortLinkMatcher {
|
||||
+fqdnRoutes *xsync.Map\[string, string\]
|
||||
+subdomainRoutes *xsync.Map\[string, emptyStruct\]
|
||||
+fqdnRoutes *xsync.Map[string, string]
|
||||
+subdomainRoutes *xsync.Map[string, struct{}]
|
||||
+ServeHTTP(w, r)
|
||||
+AddRoute(alias)
|
||||
+DelRoute(alias)
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
// including proxy protocol support, routing rules, middlewares, and access logging.
|
||||
type Config struct {
|
||||
SupportProxyProtocol bool `json:"support_proxy_protocol"`
|
||||
InboundMTLSProfile string `json:"inbound_mtls_profile,omitempty"`
|
||||
Rules struct {
|
||||
NotFound rules.Rules `json:"not_found"`
|
||||
} `json:"rules"`
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package entrypoint
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"maps"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
@@ -43,8 +41,6 @@ type Entrypoint struct {
|
||||
httpPoolDisableLog atomic.Bool
|
||||
|
||||
servers *xsync.Map[string, *httpServer] // listen addr -> server
|
||||
|
||||
inboundMTLSProfiles map[string]*x509.CertPool
|
||||
}
|
||||
|
||||
var _ entrypoint.Entrypoint = &Entrypoint{}
|
||||
@@ -73,7 +69,6 @@ func NewEntrypoint(parent task.Parent, cfg *Config) *Entrypoint {
|
||||
streamRoutes: pool.New[types.StreamRoute]("stream_routes", "stream_routes"),
|
||||
excludedRoutes: pool.New[types.Route]("excluded_routes", "excluded_routes"),
|
||||
servers: xsync.NewMap[string, *httpServer](),
|
||||
inboundMTLSProfiles: make(map[string]*x509.CertPool),
|
||||
}
|
||||
return ep
|
||||
}
|
||||
@@ -133,27 +128,14 @@ func (ep *Entrypoint) SetFindRouteDomains(domains []string) {
|
||||
func (ep *Entrypoint) SetMiddlewares(mws []map[string]any) error {
|
||||
if len(mws) == 0 {
|
||||
ep.middleware = nil
|
||||
ep.cfg.Middlewares = nil
|
||||
for _, srv := range ep.servers.Range {
|
||||
srv.resetRouteEntrypointOverlays()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
tmpMiddlewares := make([]map[string]any, len(mws))
|
||||
for i, mw := range mws {
|
||||
tmpMiddlewares[i] = maps.Clone(mw)
|
||||
}
|
||||
|
||||
mid, err := middleware.BuildMiddlewareFromChainRaw("entrypoint", mws)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ep.middleware = mid
|
||||
ep.cfg.Middlewares = tmpMiddlewares
|
||||
for _, srv := range ep.servers.Range {
|
||||
srv.resetRouteEntrypointOverlays()
|
||||
}
|
||||
|
||||
log.Debug().Msg("entrypoint middleware loaded")
|
||||
return nil
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
package entrypoint
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
)
|
||||
|
||||
func TestSetMiddlewaresInvalidatesRouteOverlayCache(t *testing.T) {
|
||||
ep := NewTestEntrypoint(t, nil)
|
||||
srv := newTestHTTPServer(t, ep)
|
||||
route := newFakeHTTPRoute(t, "test-route", "")
|
||||
route.routeMiddlewares = map[string]types.LabelMap{
|
||||
"redirectHTTP": {
|
||||
"bypass": "- path /health\n",
|
||||
},
|
||||
}
|
||||
route.handler = func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
srv.AddRoute(route)
|
||||
|
||||
require.NoError(t, ep.SetMiddlewares([]map[string]any{{
|
||||
"use": "redirectHTTP",
|
||||
}}))
|
||||
|
||||
first := httptest.NewRecorder()
|
||||
srv.ServeHTTP(first, httptest.NewRequest(http.MethodGet, "http://test-route/private", nil))
|
||||
require.Equal(t, http.StatusPermanentRedirect, first.Code)
|
||||
|
||||
require.NoError(t, ep.SetMiddlewares([]map[string]any{{
|
||||
"use": "response",
|
||||
"set_headers": map[string]string{
|
||||
"X-Overlay-Reloaded": "true",
|
||||
},
|
||||
}}))
|
||||
|
||||
second := httptest.NewRecorder()
|
||||
srv.ServeHTTP(second, httptest.NewRequest(http.MethodGet, "http://test-route/private", nil))
|
||||
require.Equal(t, http.StatusNoContent, second.Code)
|
||||
require.Equal(t, "true", second.Header().Get("X-Overlay-Reloaded"))
|
||||
}
|
||||
|
||||
func TestServeHTTPHidesEntrypointOverlayCompilationErrors(t *testing.T) {
|
||||
ep := NewTestEntrypoint(t, nil)
|
||||
srv := newTestHTTPServer(t, ep)
|
||||
route := newFakeHTTPRoute(t, "test-route", "")
|
||||
route.routeMiddlewares = map[string]types.LabelMap{
|
||||
"redirectHTTP": {
|
||||
"bypass": "not-a-valid-bypass",
|
||||
},
|
||||
}
|
||||
route.handler = func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
srv.AddRoute(route)
|
||||
|
||||
require.NoError(t, ep.SetMiddlewares([]map[string]any{{
|
||||
"use": "redirectHTTP",
|
||||
}}))
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
srv.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "http://test-route/", nil))
|
||||
|
||||
require.Equal(t, http.StatusInternalServerError, rec.Code)
|
||||
require.Equal(t, "internal server error\n", rec.Body.String())
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package entrypoint
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
errSecureRouteRequiresSNI = errors.New("secure route requires matching TLS SNI")
|
||||
errSecureRouteMisdirected = errors.New("secure route host must match TLS SNI")
|
||||
)
|
||||
@@ -3,12 +3,9 @@ package entrypoint
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
"github.com/rs/zerolog/log"
|
||||
acl "github.com/yusing/godoxy/internal/acl/types"
|
||||
autocert "github.com/yusing/godoxy/internal/autocert/types"
|
||||
@@ -38,20 +35,6 @@ type httpServer struct {
|
||||
|
||||
addr string
|
||||
routes *pool.Pool[types.HTTPRoute]
|
||||
|
||||
routeEntrypointOverlays atomic.Pointer[xsync.Map[string, *routeEntrypointOverlay]]
|
||||
}
|
||||
|
||||
type routeEntrypointOverlay struct {
|
||||
middleware *middleware.Middleware
|
||||
consumedBypass map[string]struct{}
|
||||
consumedMiddlewares map[string]struct{}
|
||||
}
|
||||
|
||||
var errNoRouteEntrypointOverlay = errors.New("no route entrypoint overlay")
|
||||
|
||||
func newRouteEntrypointOverlayMap() *xsync.Map[string, *routeEntrypointOverlay] {
|
||||
return xsync.NewMap[string, *routeEntrypointOverlay]()
|
||||
}
|
||||
|
||||
type HTTPProto string
|
||||
@@ -66,17 +49,11 @@ func NewHTTPServer(ep *Entrypoint) HTTPServer {
|
||||
}
|
||||
|
||||
func newHTTPServer(ep *Entrypoint) *httpServer {
|
||||
srv := &httpServer{ep: ep}
|
||||
srv.resetRouteEntrypointOverlays()
|
||||
return srv
|
||||
return &httpServer{ep: ep}
|
||||
}
|
||||
|
||||
// Listen starts the server and stop when entrypoint is stopped.
|
||||
func (srv *httpServer) Listen(addr string, proto HTTPProto) error {
|
||||
return srv.listen(addr, proto, nil)
|
||||
}
|
||||
|
||||
func (srv *httpServer) listen(addr string, proto HTTPProto, listener net.Listener) error {
|
||||
if srv.addr != "" {
|
||||
return errors.New("server already started")
|
||||
}
|
||||
@@ -91,12 +68,9 @@ func (srv *httpServer) listen(addr string, proto HTTPProto, listener net.Listene
|
||||
switch proto {
|
||||
case HTTPProtoHTTP:
|
||||
opts.HTTPAddr = addr
|
||||
opts.HTTPListener = listener
|
||||
case HTTPProtoHTTPS:
|
||||
opts.HTTPSAddr = addr
|
||||
opts.HTTPSListener = listener
|
||||
opts.CertProvider = autocert.FromCtx(srv.ep.task.Context())
|
||||
opts.TLSConfigMutator = srv.mutateServerTLSConfig
|
||||
}
|
||||
|
||||
task := srv.ep.task.Subtask("http_server", false)
|
||||
@@ -120,12 +94,10 @@ func (srv *httpServer) Close() {
|
||||
|
||||
func (srv *httpServer) AddRoute(route types.HTTPRoute) {
|
||||
srv.routes.Add(route)
|
||||
srv.routeEntrypointOverlayMap().Delete(route.Key())
|
||||
}
|
||||
|
||||
func (srv *httpServer) DelRoute(route types.HTTPRoute) {
|
||||
srv.routes.Del(route)
|
||||
srv.routeEntrypointOverlayMap().Delete(route.Key())
|
||||
}
|
||||
|
||||
func (srv *httpServer) FindRoute(s string) types.HTTPRoute {
|
||||
@@ -144,43 +116,14 @@ func (srv *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}()
|
||||
}
|
||||
|
||||
route, err := srv.resolveRequestRoute(r)
|
||||
route := srv.ep.findRouteFunc(srv.routes, r.Host)
|
||||
switch {
|
||||
case errors.Is(err, errSecureRouteRequiresSNI), errors.Is(err, errSecureRouteMisdirected):
|
||||
http.Error(w, err.Error(), http.StatusMisdirectedRequest)
|
||||
return
|
||||
case err != nil:
|
||||
log.Err(err).Msg("failed to resolve HTTP route")
|
||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
case route != nil:
|
||||
r = routes.WithRouteContext(r, route)
|
||||
entrypointMiddleware := srv.ep.middleware
|
||||
next := route.ServeHTTP
|
||||
if entrypointMiddleware != nil {
|
||||
overlay, err := srv.getRouteEntrypointOverlay(route)
|
||||
if err != nil && !errors.Is(err, errNoRouteEntrypointOverlay) {
|
||||
log.Err(err).Str("route", route.Name()).Msg("failed to compile route-specific entrypoint middleware")
|
||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if overlay != nil {
|
||||
entrypointMiddleware = overlay.middleware
|
||||
if len(overlay.consumedBypass) > 0 || len(overlay.consumedMiddlewares) > 0 {
|
||||
next = func(w http.ResponseWriter, req *http.Request) {
|
||||
route.ServeHTTP(w, middleware.WithConsumedRouteOverlays(
|
||||
req,
|
||||
overlay.consumedBypass,
|
||||
overlay.consumedMiddlewares,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if entrypointMiddleware != nil {
|
||||
entrypointMiddleware.ServeHTTP(next, w, r)
|
||||
if srv.ep.middleware != nil {
|
||||
srv.ep.middleware.ServeHTTP(route.ServeHTTP, w, r)
|
||||
} else {
|
||||
next(w, r)
|
||||
route.ServeHTTP(w, r)
|
||||
}
|
||||
case srv.tryHandleShortLink(w, r):
|
||||
return
|
||||
@@ -191,120 +134,6 @@ func (srv *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *httpServer) getRouteEntrypointOverlay(route types.HTTPRoute) (*routeEntrypointOverlay, error) {
|
||||
if srv.ep.middleware == nil || len(srv.ep.cfg.Middlewares) == 0 {
|
||||
return nil, errNoRouteEntrypointOverlay
|
||||
}
|
||||
overlays := srv.routeEntrypointOverlayMap()
|
||||
var buildErr error
|
||||
overlay, _ := overlays.LoadOrCompute(route.Key(), func() (*routeEntrypointOverlay, bool) {
|
||||
computed, err := srv.compileRouteEntrypointOverlay(route)
|
||||
if err != nil {
|
||||
buildErr = err
|
||||
return nil, true
|
||||
}
|
||||
return computed, false
|
||||
})
|
||||
if buildErr != nil {
|
||||
return nil, buildErr
|
||||
}
|
||||
if overlay.middleware == nil {
|
||||
return nil, errNoRouteEntrypointOverlay
|
||||
}
|
||||
return overlay, nil
|
||||
}
|
||||
|
||||
func (srv *httpServer) routeEntrypointOverlayMap() *xsync.Map[string, *routeEntrypointOverlay] {
|
||||
overlays := srv.routeEntrypointOverlays.Load()
|
||||
if overlays != nil {
|
||||
return overlays
|
||||
}
|
||||
overlays = newRouteEntrypointOverlayMap()
|
||||
if srv.routeEntrypointOverlays.CompareAndSwap(nil, overlays) {
|
||||
return overlays
|
||||
}
|
||||
return srv.routeEntrypointOverlays.Load()
|
||||
}
|
||||
|
||||
func (srv *httpServer) resetRouteEntrypointOverlays() {
|
||||
srv.routeEntrypointOverlays.Store(newRouteEntrypointOverlayMap())
|
||||
}
|
||||
|
||||
func (srv *httpServer) compileRouteEntrypointOverlay(route types.HTTPRoute) (*routeEntrypointOverlay, error) {
|
||||
routeMiddlewareMap := route.RouteMiddlewares()
|
||||
if len(routeMiddlewareMap) == 0 {
|
||||
return &routeEntrypointOverlay{}, nil
|
||||
}
|
||||
|
||||
compiled, err := middleware.BuildEntrypointRouteOverlay(
|
||||
"entrypoint",
|
||||
srv.ep.cfg.Middlewares,
|
||||
route.Name(),
|
||||
routeMiddlewareMap,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, middleware.ErrNoEntrypointRouteOverlay) {
|
||||
return &routeEntrypointOverlay{}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &routeEntrypointOverlay{
|
||||
middleware: compiled.Middleware,
|
||||
consumedBypass: compiled.ConsumedBypass,
|
||||
consumedMiddlewares: compiled.ConsumedMiddlewares,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (srv *httpServer) resolveRequestRoute(req *http.Request) (types.HTTPRoute, error) {
|
||||
hostRoute := srv.FindRoute(req.Host)
|
||||
// Skip per-route mTLS resolution if no TLS or a global mTLS profile is configured
|
||||
if req.TLS == nil || srv.ep.cfg.InboundMTLSProfile != "" {
|
||||
return hostRoute, nil
|
||||
}
|
||||
|
||||
_, hostSecure, err := srv.resolveInboundMTLSProfileForRoute(hostRoute)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverName := req.TLS.ServerName
|
||||
if serverName == "" {
|
||||
if hostSecure {
|
||||
return nil, errSecureRouteRequiresSNI
|
||||
}
|
||||
return hostRoute, nil
|
||||
}
|
||||
|
||||
sniRoute := srv.FindRoute(serverName)
|
||||
_, sniSecure, err := srv.resolveInboundMTLSProfileForRoute(sniRoute)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sniSecure {
|
||||
if !sameHTTPRoute(hostRoute, sniRoute) {
|
||||
return nil, errSecureRouteMisdirected
|
||||
}
|
||||
return sniRoute, nil
|
||||
}
|
||||
|
||||
if hostSecure {
|
||||
return nil, errSecureRouteMisdirected
|
||||
}
|
||||
return hostRoute, nil
|
||||
}
|
||||
|
||||
func sameHTTPRoute(left, right types.HTTPRoute) bool {
|
||||
switch {
|
||||
case left == nil || right == nil:
|
||||
return left == right
|
||||
case left == right:
|
||||
return true
|
||||
default:
|
||||
return left.Key() == right.Key()
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *httpServer) tryHandleShortLink(w http.ResponseWriter, r *http.Request) (handled bool) {
|
||||
host := r.Host
|
||||
if before, _, ok := strings.Cut(host, ":"); ok {
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
package entrypoint
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
func compileInboundMTLSProfiles(profiles map[string]types.InboundMTLSProfile) (map[string]*x509.CertPool, error) {
|
||||
if len(profiles) == 0 {
|
||||
return map[string]*x509.CertPool{}, nil
|
||||
}
|
||||
|
||||
compiled := make(map[string]*x509.CertPool, len(profiles))
|
||||
errs := gperr.NewBuilder("inbound mTLS profiles error")
|
||||
|
||||
for name, profile := range profiles {
|
||||
if err := profile.Validate(); err != nil {
|
||||
errs.AddSubjectf(err, "profiles.%s", name)
|
||||
continue
|
||||
}
|
||||
|
||||
pool, err := buildInboundMTLSCAPool(profile)
|
||||
if err != nil {
|
||||
errs.AddSubjectf(err, "profiles.%s", name)
|
||||
continue
|
||||
}
|
||||
compiled[name] = pool
|
||||
}
|
||||
|
||||
if err := errs.Error(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return compiled, nil
|
||||
}
|
||||
|
||||
func buildInboundMTLSCAPool(profile types.InboundMTLSProfile) (*x509.CertPool, error) {
|
||||
var pool *x509.CertPool
|
||||
|
||||
if profile.UseSystemCAs {
|
||||
systemPool, err := x509.SystemCertPool()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pool = systemPool
|
||||
}
|
||||
if pool == nil {
|
||||
pool = x509.NewCertPool()
|
||||
}
|
||||
|
||||
for _, file := range profile.CAFiles {
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, gperr.PrependSubject(err, file)
|
||||
}
|
||||
if !pool.AppendCertsFromPEM(data) {
|
||||
return nil, gperr.PrependSubject(errors.New("failed to parse CA certificates"), file)
|
||||
}
|
||||
}
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) SetInboundMTLSProfiles(profiles map[string]types.InboundMTLSProfile) error {
|
||||
compiled, err := compileInboundMTLSProfiles(profiles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if profileRef := ep.cfg.InboundMTLSProfile; profileRef != "" {
|
||||
if _, ok := compiled[profileRef]; !ok {
|
||||
return fmt.Errorf("entrypoint inbound mTLS profile %q not found", profileRef)
|
||||
}
|
||||
}
|
||||
ep.inboundMTLSProfiles = compiled
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *httpServer) mutateServerTLSConfig(base *tls.Config) *tls.Config {
|
||||
if base == nil {
|
||||
return base
|
||||
}
|
||||
|
||||
pool, enabled, err := srv.resolveInboundMTLSProfileForGlobal()
|
||||
switch {
|
||||
case err != nil:
|
||||
log.Err(err).Msg("inbound mTLS: failed to resolve global profile, falling back to per-route mTLS")
|
||||
case enabled:
|
||||
return applyInboundMTLSProfile(base, pool)
|
||||
}
|
||||
|
||||
cfg := base.Clone()
|
||||
cfg.GetConfigForClient = func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
pool, enabled, err := srv.resolveInboundMTLSProfileForServerName(hello.ServerName, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if enabled {
|
||||
return applyInboundMTLSProfile(base, pool), nil
|
||||
}
|
||||
return cloneTLSConfig(base), nil
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func applyInboundMTLSProfile(base *tls.Config, pool *x509.CertPool) *tls.Config {
|
||||
cfg := cloneTLSConfig(base)
|
||||
cfg.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
cfg.ClientCAs = pool
|
||||
return cfg
|
||||
}
|
||||
|
||||
func cloneTLSConfig(base *tls.Config) *tls.Config {
|
||||
cfg := base.Clone()
|
||||
cfg.GetConfigForClient = nil
|
||||
return cfg
|
||||
}
|
||||
|
||||
func ValidateInboundMTLSProfileRef(profileRef, globalProfile string, profiles map[string]types.InboundMTLSProfile) error {
|
||||
if profileRef == "" {
|
||||
return nil
|
||||
}
|
||||
if globalProfile != "" {
|
||||
return errors.New("route inbound_mtls_profile is not supported when entrypoint.inbound_mtls_profile is configured")
|
||||
}
|
||||
if _, ok := profiles[profileRef]; !ok {
|
||||
return fmt.Errorf("inbound mTLS profile %q not found", profileRef)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *httpServer) resolveInboundMTLSProfileForServerName(serverName string, allowGlobal bool) (pool *x509.CertPool, enabled bool, err error) {
|
||||
if serverName == "" {
|
||||
if allowGlobal {
|
||||
return srv.resolveInboundMTLSProfileForGlobal()
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
pool, enabled, err = srv.resolveInboundMTLSProfileForRoute(srv.FindRoute(serverName))
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if enabled || !allowGlobal {
|
||||
return pool, enabled, nil
|
||||
}
|
||||
return srv.resolveInboundMTLSProfileForGlobal()
|
||||
}
|
||||
|
||||
func (srv *httpServer) resolveInboundMTLSProfileForRoute(route types.HTTPRoute) (pool *x509.CertPool, enabled bool, err error) {
|
||||
if route == nil {
|
||||
return nil, false, nil
|
||||
}
|
||||
if ref := route.InboundMTLSProfileRef(); ref != "" {
|
||||
if p, ok := srv.lookupInboundMTLSProfile(ref); ok {
|
||||
return p, true, nil
|
||||
}
|
||||
return nil, false, fmt.Errorf("route %q inbound mTLS profile %q not found", route.Name(), ref)
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
func (srv *httpServer) resolveInboundMTLSProfileForGlobal() (pool *x509.CertPool, enabled bool, err error) {
|
||||
if globalRef := srv.ep.cfg.InboundMTLSProfile; globalRef != "" {
|
||||
if p, ok := srv.lookupInboundMTLSProfile(globalRef); ok {
|
||||
return p, true, nil
|
||||
}
|
||||
return nil, false, fmt.Errorf("entrypoint inbound mTLS profile %q not found", globalRef)
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
func (srv *httpServer) lookupInboundMTLSProfile(ref string) (*x509.CertPool, bool) {
|
||||
if len(srv.ep.inboundMTLSProfiles) == 0 { // nil or empty map
|
||||
return nil, false
|
||||
}
|
||||
pool, ok := srv.ep.inboundMTLSProfiles[ref]
|
||||
return pool, ok
|
||||
}
|
||||
@@ -1,556 +0,0 @@
|
||||
package entrypoint
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/require"
|
||||
agentcert "github.com/yusing/godoxy/agent/pkg/agent"
|
||||
"github.com/yusing/godoxy/internal/agentpool"
|
||||
autocert "github.com/yusing/godoxy/internal/autocert/types"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
"github.com/yusing/godoxy/internal/homepage"
|
||||
nettypes "github.com/yusing/godoxy/internal/net/types"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
"github.com/yusing/goutils/pool"
|
||||
"github.com/yusing/goutils/task"
|
||||
)
|
||||
|
||||
type fakeHTTPRoute struct {
|
||||
key string
|
||||
name string
|
||||
inboundMTLSProfile string
|
||||
listenURL *nettypes.URL
|
||||
routeMiddlewares map[string]types.LabelMap
|
||||
handler http.HandlerFunc
|
||||
task *task.Task
|
||||
}
|
||||
|
||||
func newFakeHTTPRoute(t *testing.T, alias, profile string) *fakeHTTPRoute {
|
||||
return newFakeHTTPRouteAt(t, alias, profile, "https://:1000")
|
||||
}
|
||||
|
||||
func newFakeHTTPRouteAt(t *testing.T, alias, profile, listenURL string) *fakeHTTPRoute {
|
||||
t.Helper()
|
||||
|
||||
return &fakeHTTPRoute{
|
||||
key: alias,
|
||||
name: alias,
|
||||
inboundMTLSProfile: profile,
|
||||
listenURL: nettypes.MustParseURL(listenURL),
|
||||
task: task.GetTestTask(t),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *fakeHTTPRoute) Key() string { return r.key }
|
||||
func (r *fakeHTTPRoute) Name() string { return r.name }
|
||||
func (r *fakeHTTPRoute) Start(task.Parent) error { return nil }
|
||||
func (r *fakeHTTPRoute) Task() *task.Task { return r.task }
|
||||
func (r *fakeHTTPRoute) Finish(any) {
|
||||
// no-op: test stub
|
||||
}
|
||||
func (r *fakeHTTPRoute) MarshalZerologObject(*zerolog.Event) {
|
||||
// no-op: test stub
|
||||
}
|
||||
func (r *fakeHTTPRoute) ProviderName() string { return "" }
|
||||
func (r *fakeHTTPRoute) GetProvider() types.RouteProvider { return nil }
|
||||
func (r *fakeHTTPRoute) ListenURL() *nettypes.URL { return r.listenURL }
|
||||
func (r *fakeHTTPRoute) TargetURL() *nettypes.URL { return nil }
|
||||
func (r *fakeHTTPRoute) HealthMonitor() types.HealthMonitor { return nil }
|
||||
func (r *fakeHTTPRoute) SetHealthMonitor(types.HealthMonitor) {
|
||||
// no-op: test stub
|
||||
}
|
||||
func (r *fakeHTTPRoute) References() []string { return nil }
|
||||
func (r *fakeHTTPRoute) ShouldExclude() bool { return false }
|
||||
func (r *fakeHTTPRoute) Started() <-chan struct{} { return nil }
|
||||
func (r *fakeHTTPRoute) IdlewatcherConfig() *types.IdlewatcherConfig { return nil }
|
||||
func (r *fakeHTTPRoute) HealthCheckConfig() types.HealthCheckConfig { return types.HealthCheckConfig{} }
|
||||
func (r *fakeHTTPRoute) LoadBalanceConfig() *types.LoadBalancerConfig {
|
||||
return nil
|
||||
}
|
||||
func (r *fakeHTTPRoute) HomepageItem() homepage.Item { return homepage.Item{} }
|
||||
func (r *fakeHTTPRoute) DisplayName() string { return r.name }
|
||||
func (r *fakeHTTPRoute) ContainerInfo() *types.Container {
|
||||
return nil
|
||||
}
|
||||
func (r *fakeHTTPRoute) GetAgent() *agentpool.Agent { return nil }
|
||||
func (r *fakeHTTPRoute) IsDocker() bool { return false }
|
||||
func (r *fakeHTTPRoute) IsAgent() bool { return false }
|
||||
func (r *fakeHTTPRoute) UseLoadBalance() bool { return false }
|
||||
func (r *fakeHTTPRoute) UseIdleWatcher() bool { return false }
|
||||
func (r *fakeHTTPRoute) UseHealthCheck() bool { return false }
|
||||
func (r *fakeHTTPRoute) UseAccessLog() bool { return false }
|
||||
func (r *fakeHTTPRoute) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
if r.handler != nil {
|
||||
r.handler(w, req)
|
||||
}
|
||||
}
|
||||
func (r *fakeHTTPRoute) InboundMTLSProfileRef() string { return r.inboundMTLSProfile }
|
||||
func (r *fakeHTTPRoute) RouteMiddlewares() map[string]types.LabelMap { return r.routeMiddlewares }
|
||||
|
||||
func newTestHTTPServer(t *testing.T, ep *Entrypoint) *httpServer {
|
||||
t.Helper()
|
||||
|
||||
srv, ok := ep.servers.Load(common.ProxyHTTPAddr)
|
||||
if ok {
|
||||
return srv
|
||||
}
|
||||
|
||||
srv = &httpServer{
|
||||
ep: ep,
|
||||
addr: common.ProxyHTTPAddr,
|
||||
routes: pool.New[types.HTTPRoute]("test-http-routes", "test-http-routes"),
|
||||
}
|
||||
srv.resetRouteEntrypointOverlays()
|
||||
ep.servers.Store(common.ProxyHTTPAddr, srv)
|
||||
return srv
|
||||
}
|
||||
|
||||
func TestMutateServerTLSConfigWithGlobalProfile(t *testing.T) {
|
||||
ep := NewTestEntrypoint(t, &Config{InboundMTLSProfile: "global"})
|
||||
srv := newTestHTTPServer(t, ep)
|
||||
require.NoError(t, ep.SetInboundMTLSProfiles(map[string]types.InboundMTLSProfile{
|
||||
"global": {UseSystemCAs: true},
|
||||
}))
|
||||
|
||||
base := &tls.Config{MinVersion: tls.VersionTLS12}
|
||||
mutated := srv.mutateServerTLSConfig(base)
|
||||
|
||||
require.Equal(t, tls.RequireAndVerifyClientCert, mutated.ClientAuth)
|
||||
require.NotNil(t, mutated.ClientCAs)
|
||||
require.Nil(t, mutated.GetConfigForClient)
|
||||
}
|
||||
|
||||
func TestMutateServerTLSConfigWithoutProfilesKeepsTLSOpen(t *testing.T) {
|
||||
ep := NewTestEntrypoint(t, nil)
|
||||
srv := newTestHTTPServer(t, ep)
|
||||
require.NoError(t, ep.SetInboundMTLSProfiles(nil))
|
||||
|
||||
base := &tls.Config{MinVersion: tls.VersionTLS12}
|
||||
mutated := srv.mutateServerTLSConfig(base)
|
||||
|
||||
require.Zero(t, mutated.ClientAuth)
|
||||
require.Nil(t, mutated.ClientCAs)
|
||||
require.NotNil(t, mutated.GetConfigForClient)
|
||||
|
||||
cfg, err := mutated.GetConfigForClient(&tls.ClientHelloInfo{})
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, cfg.ClientAuth)
|
||||
require.Nil(t, cfg.ClientCAs)
|
||||
require.Nil(t, cfg.GetConfigForClient)
|
||||
}
|
||||
|
||||
func TestMutateServerTLSConfigWithRouteProfiles(t *testing.T) {
|
||||
ep := NewTestEntrypoint(t, nil)
|
||||
ep.SetFindRouteDomains([]string{".example.com"})
|
||||
srv := newTestHTTPServer(t, ep)
|
||||
srv.AddRoute(newFakeHTTPRoute(t, "secure-app", "route"))
|
||||
srv.AddRoute(newFakeHTTPRoute(t, "open-app", ""))
|
||||
require.NoError(t, ep.SetInboundMTLSProfiles(map[string]types.InboundMTLSProfile{
|
||||
"route": {UseSystemCAs: true},
|
||||
}))
|
||||
|
||||
base := &tls.Config{MinVersion: tls.VersionTLS12}
|
||||
mutated := srv.mutateServerTLSConfig(base)
|
||||
|
||||
require.Zero(t, mutated.ClientAuth)
|
||||
require.Nil(t, mutated.ClientCAs)
|
||||
require.NotNil(t, mutated.GetConfigForClient)
|
||||
|
||||
secureCfg, err := mutated.GetConfigForClient(&tls.ClientHelloInfo{ServerName: "secure-app.example.com"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tls.RequireAndVerifyClientCert, secureCfg.ClientAuth)
|
||||
require.NotNil(t, secureCfg.ClientCAs)
|
||||
require.Nil(t, secureCfg.GetConfigForClient)
|
||||
|
||||
openCfg, err := mutated.GetConfigForClient(&tls.ClientHelloInfo{ServerName: "open-app.example.com"})
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, openCfg.ClientAuth)
|
||||
require.Nil(t, openCfg.ClientCAs)
|
||||
require.Nil(t, openCfg.GetConfigForClient)
|
||||
|
||||
unknownCfg, err := mutated.GetConfigForClient(&tls.ClientHelloInfo{ServerName: "unknown.example.com"})
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, unknownCfg.ClientAuth)
|
||||
require.Nil(t, unknownCfg.ClientCAs)
|
||||
require.Nil(t, unknownCfg.GetConfigForClient)
|
||||
}
|
||||
|
||||
func TestMutateServerTLSConfigFallsBackToRouteProfilesAfterGlobalLookupError(t *testing.T) {
|
||||
ep := NewTestEntrypoint(t, &Config{InboundMTLSProfile: "missing"})
|
||||
ep.SetFindRouteDomains([]string{".example.com"})
|
||||
srv := newTestHTTPServer(t, ep)
|
||||
srv.AddRoute(newFakeHTTPRoute(t, "secure-app", "route"))
|
||||
ep.inboundMTLSProfiles = map[string]*x509.CertPool{
|
||||
"route": x509.NewCertPool(),
|
||||
}
|
||||
|
||||
base := &tls.Config{MinVersion: tls.VersionTLS12}
|
||||
mutated := srv.mutateServerTLSConfig(base)
|
||||
|
||||
require.NotNil(t, mutated.GetConfigForClient)
|
||||
|
||||
secureCfg, err := mutated.GetConfigForClient(&tls.ClientHelloInfo{ServerName: "secure-app.example.com"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tls.RequireAndVerifyClientCert, secureCfg.ClientAuth)
|
||||
require.NotNil(t, secureCfg.ClientCAs)
|
||||
require.Nil(t, secureCfg.GetConfigForClient)
|
||||
|
||||
openCfg, err := mutated.GetConfigForClient(&tls.ClientHelloInfo{ServerName: "open-app.example.com"})
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, openCfg.ClientAuth)
|
||||
require.Nil(t, openCfg.ClientCAs)
|
||||
require.Nil(t, openCfg.GetConfigForClient)
|
||||
}
|
||||
|
||||
func TestSetInboundMTLSProfilesRejectsUnknownGlobalProfile(t *testing.T) {
|
||||
ep := NewTestEntrypoint(t, &Config{InboundMTLSProfile: "missing"})
|
||||
err := ep.SetInboundMTLSProfiles(map[string]types.InboundMTLSProfile{
|
||||
"known": {UseSystemCAs: true},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, `entrypoint inbound mTLS profile "missing" not found`)
|
||||
}
|
||||
|
||||
func TestSetInboundMTLSProfilesRejectsBadCAFile(t *testing.T) {
|
||||
ep := NewTestEntrypoint(t, &Config{InboundMTLSProfile: "broken"})
|
||||
err := ep.SetInboundMTLSProfiles(map[string]types.InboundMTLSProfile{
|
||||
"broken": {CAFiles: []string{filepath.Join(t.TempDir(), "missing.pem")}},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "missing.pem")
|
||||
}
|
||||
|
||||
func TestCompileInboundMTLSProfilesReturnsNilMapOnError(t *testing.T) {
|
||||
compiled, err := compileInboundMTLSProfiles(map[string]types.InboundMTLSProfile{
|
||||
"ok": {UseSystemCAs: true},
|
||||
"bad": {CAFiles: []string{filepath.Join(t.TempDir(), "missing.pem")}},
|
||||
})
|
||||
require.Nil(t, compiled)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "missing.pem")
|
||||
}
|
||||
|
||||
func TestMutateServerTLSConfigRejectsUnknownRouteProfile(t *testing.T) {
|
||||
ep := NewTestEntrypoint(t, nil)
|
||||
ep.SetFindRouteDomains([]string{".example.com"})
|
||||
srv := newTestHTTPServer(t, ep)
|
||||
srv.AddRoute(newFakeHTTPRoute(t, "secure-app", "missing"))
|
||||
|
||||
base := &tls.Config{MinVersion: tls.VersionTLS12}
|
||||
mutated := srv.mutateServerTLSConfig(base)
|
||||
|
||||
_, err := mutated.GetConfigForClient(&tls.ClientHelloInfo{ServerName: "secure-app.example.com"})
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, `route "secure-app" inbound mTLS profile "missing" not found`)
|
||||
}
|
||||
|
||||
func TestResolveRequestRouteRejectsUnknownRouteProfile(t *testing.T) {
|
||||
ep := NewTestEntrypoint(t, nil)
|
||||
ep.SetFindRouteDomains([]string{".example.com"})
|
||||
srv := newTestHTTPServer(t, ep)
|
||||
srv.AddRoute(newFakeHTTPRoute(t, "secure-app", "missing"))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "https://secure-app.example.com", nil)
|
||||
req.Host = "secure-app.example.com"
|
||||
req.TLS = &tls.ConnectionState{ServerName: "secure-app.example.com"}
|
||||
|
||||
route, err := srv.resolveRequestRoute(req)
|
||||
require.Nil(t, route)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, `route "secure-app" inbound mTLS profile "missing" not found`)
|
||||
}
|
||||
|
||||
func TestResolveRequestRouteLeavesOpenAndUnknownHostsUnchanged(t *testing.T) {
|
||||
ep := NewTestEntrypoint(t, nil)
|
||||
ep.SetFindRouteDomains([]string{".example.com"})
|
||||
srv := newTestHTTPServer(t, ep)
|
||||
srv.AddRoute(newFakeHTTPRoute(t, "secure-app", "route"))
|
||||
srv.AddRoute(newFakeHTTPRoute(t, "open-app", ""))
|
||||
require.NoError(t, ep.SetInboundMTLSProfiles(map[string]types.InboundMTLSProfile{
|
||||
"route": {UseSystemCAs: true},
|
||||
}))
|
||||
|
||||
t.Run("open host stays open", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "https://open-app.example.com", nil)
|
||||
req.Host = "open-app.example.com"
|
||||
req.TLS = &tls.ConnectionState{ServerName: "open-app.example.com"}
|
||||
|
||||
route, err := srv.resolveRequestRoute(req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, route)
|
||||
require.Equal(t, "open-app", route.Name())
|
||||
})
|
||||
|
||||
t.Run("unknown host falls through", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "https://unknown.example.com", nil)
|
||||
req.Host = "unknown.example.com"
|
||||
req.TLS = &tls.ConnectionState{ServerName: "unknown.example.com"}
|
||||
|
||||
route, err := srv.resolveRequestRoute(req)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, route)
|
||||
})
|
||||
}
|
||||
|
||||
func TestInboundMTLSGlobalHandshake(t *testing.T) {
|
||||
ca, srv, client, err := agentcert.NewAgent()
|
||||
require.NoError(t, err)
|
||||
|
||||
serverCert, err := srv.ToTLSCert()
|
||||
require.NoError(t, err)
|
||||
clientCert, err := client.ToTLSCert()
|
||||
require.NoError(t, err)
|
||||
|
||||
caPath := writeTempFile(t, "ca.pem", ca.Cert)
|
||||
provider := &staticCertProvider{cert: serverCert}
|
||||
|
||||
ep := NewTestEntrypoint(t, &Config{InboundMTLSProfile: "global"})
|
||||
t.Cleanup(func() {
|
||||
closeTestServers(t, ep)
|
||||
})
|
||||
autocert.SetCtx(task.GetTestTask(t), provider)
|
||||
require.NoError(t, ep.SetInboundMTLSProfiles(map[string]types.InboundMTLSProfile{
|
||||
"global": {CAFiles: []string{caPath}},
|
||||
}))
|
||||
|
||||
listener, releaseListener := reserveTCPAddr(t)
|
||||
listenAddr := listener.Addr().String()
|
||||
addHTTPRouteAt(t, ep, "app1", "", listenAddr, listener)
|
||||
releaseListener()
|
||||
|
||||
t.Run("trusted client succeeds", func(t *testing.T) {
|
||||
resp, err := doHTTPSRequest(listenAddr, "app1.example.com", &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
Certificates: []tls.Certificate{*clientCert},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
_ = resp.Body.Close()
|
||||
})
|
||||
|
||||
t.Run("missing client cert fails handshake", func(t *testing.T) {
|
||||
_, err := doHTTPSRequest(listenAddr, "app1.example.com", &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("wrong client cert fails handshake", func(t *testing.T) {
|
||||
_, _, badClient, err := agentcert.NewAgent()
|
||||
require.NoError(t, err)
|
||||
badClientCert, err := badClient.ToTLSCert()
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = doHTTPSRequest(listenAddr, "app1.example.com", &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
Certificates: []tls.Certificate{*badClientCert},
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestInboundMTLSRouteScopedHandshake(t *testing.T) {
|
||||
ca, srv, client, err := agentcert.NewAgent()
|
||||
require.NoError(t, err)
|
||||
|
||||
serverCert, err := srv.ToTLSCert()
|
||||
require.NoError(t, err)
|
||||
clientCert, err := client.ToTLSCert()
|
||||
require.NoError(t, err)
|
||||
|
||||
caPath := writeTempFile(t, "ca.pem", ca.Cert)
|
||||
provider := &staticCertProvider{cert: serverCert}
|
||||
|
||||
ep := NewTestEntrypoint(t, nil)
|
||||
t.Cleanup(func() {
|
||||
closeTestServers(t, ep)
|
||||
})
|
||||
ep.SetFindRouteDomains([]string{".example.com"})
|
||||
autocert.SetCtx(task.GetTestTask(t), provider)
|
||||
require.NoError(t, ep.SetInboundMTLSProfiles(map[string]types.InboundMTLSProfile{
|
||||
"route": {CAFiles: []string{caPath}},
|
||||
}))
|
||||
|
||||
listener, releaseListener := reserveTCPAddr(t)
|
||||
listenAddr := listener.Addr().String()
|
||||
addHTTPRouteAt(t, ep, "secure-app", "route", listenAddr, listener)
|
||||
releaseListener()
|
||||
addHTTPRouteAt(t, ep, "open-app", "", listenAddr, nil)
|
||||
|
||||
t.Run("secure route requires client cert when sni matches", func(t *testing.T) {
|
||||
_, err := doHTTPSRequest(listenAddr, "secure-app.example.com", &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("secure route accepts trusted client cert", func(t *testing.T) {
|
||||
resp, err := doHTTPSRequest(listenAddr, "secure-app.example.com", &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
Certificates: []tls.Certificate{*clientCert},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
_ = resp.Body.Close()
|
||||
})
|
||||
|
||||
t.Run("open route without client cert succeeds", func(t *testing.T) {
|
||||
resp, err := doHTTPSRequest(listenAddr, "open-app.example.com", &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
_ = resp.Body.Close()
|
||||
})
|
||||
|
||||
t.Run("secure route rejects requests without sni", func(t *testing.T) {
|
||||
resp, tlsConn, err := doHTTPSRequestWithServerName(listenAddr, "secure-app.example.com", "", &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = tlsConn.Close() }()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
require.Equal(t, http.StatusMisdirectedRequest, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("secure route rejects host and sni mismatch without cert", func(t *testing.T) {
|
||||
resp, tlsConn, err := doHTTPSRequestWithServerName(listenAddr, "secure-app.example.com", "open-app.example.com", &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = tlsConn.Close() }()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
require.Equal(t, http.StatusMisdirectedRequest, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("open route rejects host and sni mismatch when sni selects secure route", func(t *testing.T) {
|
||||
resp, tlsConn, err := doHTTPSRequestWithServerName(listenAddr, "open-app.example.com", "secure-app.example.com", &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
Certificates: []tls.Certificate{*clientCert},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = tlsConn.Close() }()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
require.Equal(t, http.StatusMisdirectedRequest, resp.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
func addHTTPRouteAt(t *testing.T, ep *Entrypoint, alias, profile, listenAddr string, listener net.Listener) {
|
||||
t.Helper()
|
||||
|
||||
route := newFakeHTTPRouteAt(t, alias, profile, "https://"+listenAddr)
|
||||
if listener == nil {
|
||||
require.NoError(t, ep.StartAddRoute(route))
|
||||
return
|
||||
}
|
||||
require.NoError(t, ep.addHTTPRouteWithListener(route, listenAddr, HTTPProtoHTTPS, listener))
|
||||
}
|
||||
|
||||
func closeTestServers(t *testing.T, ep *Entrypoint) {
|
||||
t.Helper()
|
||||
for _, srv := range ep.servers.Range {
|
||||
srv.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func reserveTCPAddr(t *testing.T) (net.Listener, func()) {
|
||||
t.Helper()
|
||||
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
owned := true
|
||||
t.Cleanup(func() {
|
||||
if owned {
|
||||
_ = ln.Close()
|
||||
}
|
||||
})
|
||||
return ln, func() {
|
||||
owned = false
|
||||
}
|
||||
}
|
||||
|
||||
func writeTempFile(t *testing.T, name string, data []byte) string {
|
||||
t.Helper()
|
||||
path := filepath.Join(t.TempDir(), name)
|
||||
require.NoError(t, os.WriteFile(path, data, 0o600))
|
||||
return path
|
||||
}
|
||||
|
||||
func doHTTPSRequest(addr, host string, tlsConfig *tls.Config) (*http.Response, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, "https://"+addr, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Host = host
|
||||
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: cloneTLSConfigWithServerName(tlsConfig, host),
|
||||
},
|
||||
}
|
||||
return client.Do(req)
|
||||
}
|
||||
|
||||
// doHTTPSRequestWithServerName sends GET https://addr/ with HTTP Host set to host and TLS
|
||||
// ServerName set to serverName (SNI may differ from Host). The returned connection stays open
|
||||
// until the caller closes it after finishing with resp (typically close resp.Body first, then
|
||||
// the tls connection).
|
||||
func doHTTPSRequestWithServerName(addr, host, serverName string, tlsConfig *tls.Config) (*http.Response, io.Closer, error) {
|
||||
conn, err := tls.Dial("tcp", addr, cloneTLSConfigWithServerName(tlsConfig, serverName))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "https://"+addr, nil)
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
req.Host = host
|
||||
if err := req.Write(conn); err != nil {
|
||||
_ = conn.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
return resp, conn, nil
|
||||
}
|
||||
|
||||
func cloneTLSConfigWithServerName(cfg *tls.Config, serverName string) *tls.Config {
|
||||
if cfg == nil {
|
||||
cfg = &tls.Config{}
|
||||
}
|
||||
cloned := cfg.Clone()
|
||||
cloned.ServerName = serverName
|
||||
return cloned
|
||||
}
|
||||
|
||||
type staticCertProvider struct {
|
||||
cert *tls.Certificate
|
||||
}
|
||||
|
||||
func (p *staticCertProvider) GetCert(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return p.cert, nil
|
||||
}
|
||||
func (p *staticCertProvider) GetCertInfos() ([]autocert.CertInfo, error) { return nil, nil }
|
||||
func (p *staticCertProvider) ScheduleRenewalAll(task.Parent) {
|
||||
// no-op: test stub
|
||||
}
|
||||
func (p *staticCertProvider) ObtainCertAll() error { return nil }
|
||||
func (p *staticCertProvider) ForceExpiryAll() bool { return false }
|
||||
func (p *staticCertProvider) WaitRenewalDone(context.Context) bool { return true }
|
||||
@@ -113,14 +113,10 @@ func (ep *Entrypoint) AddHTTPRoute(route types.HTTPRoute) error {
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) addHTTPRoute(route types.HTTPRoute, addr string, proto HTTPProto) error {
|
||||
return ep.addHTTPRouteWithListener(route, addr, proto, nil)
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) addHTTPRouteWithListener(route types.HTTPRoute, addr string, proto HTTPProto, listener net.Listener) error {
|
||||
var err error
|
||||
srv, _ := ep.servers.LoadOrCompute(addr, func() (newSrv *httpServer, cancel bool) {
|
||||
newSrv = newHTTPServer(ep)
|
||||
err = newSrv.listen(addr, proto, listener)
|
||||
err = newSrv.Listen(addr, proto)
|
||||
cancel = err != nil
|
||||
return
|
||||
})
|
||||
|
||||
Submodule internal/go-oidc updated: bcfa54222d...15707eda85
Submodule internal/go-proxmox updated: d07315e06a...d13c6c346d
Submodule internal/gopsutil updated: 1636a83ac2...edfb472ac5
@@ -6,7 +6,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"encoding/json"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
@@ -108,7 +108,7 @@ func interceptDockerInspectResponse(resp *http.Response) (intercepted bool, err
|
||||
}
|
||||
|
||||
var state container.State
|
||||
err = json.Unmarshal(body, &state)
|
||||
err = sonic.Unmarshal(body, &state)
|
||||
release(body)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"encoding/json"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/lithammer/fuzzysearch/fuzzy"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
@@ -55,7 +55,7 @@ func init() {
|
||||
|
||||
func InitCache() {
|
||||
m := make(IconMap)
|
||||
err := serialization.LoadFileIfExist(common.IconListCachePath, &m, json.Unmarshal)
|
||||
err := serialization.LoadFileIfExist(common.IconListCachePath, &m, sonic.Unmarshal)
|
||||
switch {
|
||||
case err != nil:
|
||||
// backward compatible
|
||||
@@ -63,13 +63,13 @@ func InitCache() {
|
||||
Icons IconMap
|
||||
LastUpdate time.Time
|
||||
}{}
|
||||
err = serialization.LoadFileIfExist(common.IconListCachePath, &oldFormat, json.Unmarshal)
|
||||
err = serialization.LoadFileIfExist(common.IconListCachePath, &oldFormat, sonic.Unmarshal)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to load icons")
|
||||
} else {
|
||||
m = oldFormat.Icons
|
||||
// store it to disk immediately
|
||||
_ = serialization.SaveFile(common.IconListCachePath, &m, 0o644, json.Marshal)
|
||||
_ = serialization.SaveFile(common.IconListCachePath, &m, 0o644, sonic.Marshal)
|
||||
}
|
||||
case len(m) > 0:
|
||||
log.Info().
|
||||
@@ -85,7 +85,7 @@ func InitCache() {
|
||||
|
||||
task.OnProgramExit("save_icons_cache", func() {
|
||||
icons := iconsCache.Load()
|
||||
_ = serialization.SaveFile(common.IconListCachePath, &icons, 0o644, json.Marshal)
|
||||
_ = serialization.SaveFile(common.IconListCachePath, &icons, 0o644, sonic.Marshal)
|
||||
})
|
||||
|
||||
go backgroundUpdateIcons()
|
||||
@@ -106,7 +106,7 @@ func backgroundUpdateIcons() {
|
||||
// swap old cache with new cache
|
||||
iconsCache.Store(newCache)
|
||||
// save it to disk
|
||||
err := serialization.SaveFile(common.IconListCachePath, &newCache, 0o644, json.Marshal)
|
||||
err := serialization.SaveFile(common.IconListCachePath, &newCache, 0o644, sonic.Marshal)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("failed to save icons")
|
||||
}
|
||||
@@ -286,7 +286,7 @@ func UpdateWalkxCodeIcons(m IconMap) error {
|
||||
}
|
||||
|
||||
data := make(map[string][]string)
|
||||
err = json.Unmarshal(body, &data)
|
||||
err = sonic.Unmarshal(body, &data)
|
||||
release(body)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -365,7 +365,7 @@ func UpdateSelfhstIcons(m IconMap) error {
|
||||
}
|
||||
|
||||
data := make([]SelfhStIcon, 0)
|
||||
err = json.Unmarshal(body, &data) //nolint:musttag
|
||||
err = sonic.Unmarshal(body, &data) //nolint:musttag
|
||||
release(body)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"encoding/json"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/yusing/godoxy/internal/homepage/widgets"
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
)
|
||||
@@ -71,7 +71,7 @@ func jsonRequest[T any](ctx context.Context, client *Client, endpoint string, qu
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
err = sonic.ConfigDefault.NewDecoder(resp.Body).Decode(&result)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"encoding/json"
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
const endpointLogs = "/api/v2/log/main"
|
||||
@@ -45,7 +45,7 @@ func (l *LogEntry) Level() string {
|
||||
}
|
||||
|
||||
func (l *LogEntry) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
return sonic.Marshal(map[string]any{
|
||||
"id": l.ID,
|
||||
"timestamp": l.Timestamp,
|
||||
"level": l.Level(),
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"iter"
|
||||
"strconv"
|
||||
|
||||
"encoding/json"
|
||||
"github.com/bytedance/sonic"
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
)
|
||||
|
||||
@@ -14,7 +14,7 @@ type watcherDebug struct {
|
||||
|
||||
func (w watcherDebug) MarshalJSON() ([]byte, error) {
|
||||
state := w.state.Load()
|
||||
return json.Marshal(map[string]any{
|
||||
return sonic.Marshal(map[string]any{
|
||||
"name": w.Name(),
|
||||
"state": map[string]string{
|
||||
"status": string(state.status),
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"encoding/json"
|
||||
"github.com/bytedance/sonic"
|
||||
gevents "github.com/yusing/goutils/events"
|
||||
)
|
||||
|
||||
@@ -26,7 +26,7 @@ const (
|
||||
)
|
||||
|
||||
func writeSSE(w io.Writer, v any) error {
|
||||
data, err := json.Marshal(v)
|
||||
data, err := sonic.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -16,6 +16,21 @@ import (
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
type ForceCacheControl struct {
|
||||
expires string
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (f *ForceCacheControl) WriteHeader(code int) {
|
||||
f.ResponseWriter.Header().Set("Cache-Control", "must-revalidate")
|
||||
f.ResponseWriter.Header().Set("Expires", f.expires)
|
||||
f.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (f *ForceCacheControl) Unwrap() http.ResponseWriter {
|
||||
return f.ResponseWriter
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler.
|
||||
func (w *Watcher) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
shouldNext := w.wakeFromHTTP(rw, r)
|
||||
@@ -26,14 +41,15 @@ func (w *Watcher) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
case <-r.Context().Done():
|
||||
return
|
||||
default:
|
||||
w.rp.ServeHTTP(rw, r)
|
||||
f := &ForceCacheControl{expires: w.expires().Format(http.TimeFormat), ResponseWriter: rw}
|
||||
w.rp.ServeHTTP(f, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Watcher) handleWakeEventsSSE(rw http.ResponseWriter, r *http.Request) {
|
||||
// Set SSE headers
|
||||
rw.Header().Set("Content-Type", "text/event-stream")
|
||||
setNoStoreHeaders(rw.Header())
|
||||
rw.Header().Set("Cache-Control", "no-cache")
|
||||
rw.Header().Set("Connection", "keep-alive")
|
||||
rw.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
rw.Header().Set("Access-Control-Allow-Headers", "Cache-Control")
|
||||
@@ -111,15 +127,12 @@ func (w *Watcher) wakeFromHTTP(rw http.ResponseWriter, r *http.Request) (shouldN
|
||||
fmt.Fprint(rw, err)
|
||||
return false
|
||||
}
|
||||
setNoStoreHeaders(rw.Header())
|
||||
serveStaticContent(rw, result.StatusCode, result.ContentType(), result.Icon)
|
||||
return false
|
||||
case idlewatcher.LoadingPageCSSPath:
|
||||
setNoStoreHeaders(rw.Header())
|
||||
serveStaticContent(rw, http.StatusOK, "text/css", cssBytes)
|
||||
return false
|
||||
case idlewatcher.LoadingPageJSPath:
|
||||
setNoStoreHeaders(rw.Header())
|
||||
serveStaticContent(rw, http.StatusOK, "application/javascript", jsBytes)
|
||||
return false
|
||||
case idlewatcher.WakeEventsPath:
|
||||
@@ -165,7 +178,9 @@ func (w *Watcher) wakeFromHTTP(rw http.ResponseWriter, r *http.Request) (shouldN
|
||||
|
||||
// Send a loading response to the client
|
||||
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
setNoStoreHeaders(rw.Header())
|
||||
rw.Header().Set("Cache-Control", "no-cache")
|
||||
rw.Header().Add("Cache-Control", "no-store")
|
||||
rw.Header().Add("Cache-Control", "must-revalidate")
|
||||
rw.Header().Add("Connection", "close")
|
||||
_ = w.writeLoadingPage(rw)
|
||||
return false
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
package idlewatcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
idlewatchertypes "github.com/yusing/godoxy/internal/idlewatcher/types"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
gevents "github.com/yusing/goutils/events"
|
||||
"github.com/yusing/goutils/http/reverseproxy"
|
||||
)
|
||||
|
||||
func TestWriteLoadingPageDisablesCaching(t *testing.T) {
|
||||
w := newTestWatcher(t)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
err := w.writeLoadingPage(rec)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "no-store, no-cache, must-revalidate, max-age=0", rec.Header().Get("Cache-Control"))
|
||||
require.Equal(t, "no-cache", rec.Header().Get("Pragma"))
|
||||
require.Equal(t, "0", rec.Header().Get("Expires"))
|
||||
}
|
||||
|
||||
func TestServeHTTPLoadingEndpointsDisableCaching(t *testing.T) {
|
||||
for _, path := range []string{
|
||||
idlewatchertypes.LoadingPageCSSPath,
|
||||
idlewatchertypes.LoadingPageJSPath,
|
||||
idlewatchertypes.WakeEventsPath,
|
||||
} {
|
||||
t.Run(path, func(t *testing.T) {
|
||||
w := newTestWatcher(t)
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "http://example.com"+path, nil)
|
||||
if path == idlewatchertypes.WakeEventsPath {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
cancel()
|
||||
req = req.WithContext(ctx)
|
||||
}
|
||||
|
||||
w.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, "no-store, no-cache, must-revalidate, max-age=0", rec.Header().Get("Cache-Control"))
|
||||
require.Equal(t, "no-cache", rec.Header().Get("Pragma"))
|
||||
require.Equal(t, "0", rec.Header().Get("Expires"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeHTTPReadyProxyPreservesUpstreamCacheHeaders(t *testing.T) {
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
|
||||
rw.Header().Set("Cache-Control", "public, max-age=600")
|
||||
rw.Header().Set("Expires", "Tue, 21 Apr 2026 00:00:00 GMT")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
_, _ = rw.Write([]byte("ok"))
|
||||
}))
|
||||
defer upstream.Close()
|
||||
|
||||
targetURL, err := url.Parse(upstream.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
w := newTestWatcher(t)
|
||||
w.rp = reverseproxy.NewReverseProxy("idlewatcher-test", targetURL, upstream.Client().Transport)
|
||||
w.state.Store(&containerState{
|
||||
status: idlewatchertypes.ContainerStatusRunning,
|
||||
ready: true,
|
||||
})
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "http://example.com/", nil)
|
||||
|
||||
w.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, "public, max-age=600", rec.Header().Get("Cache-Control"))
|
||||
require.Equal(t, "Tue, 21 Apr 2026 00:00:00 GMT", rec.Header().Get("Expires"))
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
require.Equal(t, "ok", rec.Body.String())
|
||||
}
|
||||
|
||||
func newTestWatcher(t *testing.T) *Watcher {
|
||||
t.Helper()
|
||||
|
||||
ticker := time.NewTicker(time.Hour)
|
||||
t.Cleanup(ticker.Stop)
|
||||
|
||||
w := &Watcher{
|
||||
cfg: &types.IdlewatcherConfig{
|
||||
IdlewatcherProviderConfig: types.IdlewatcherProviderConfig{
|
||||
Docker: &types.DockerConfig{
|
||||
ContainerName: "test-container",
|
||||
},
|
||||
},
|
||||
IdlewatcherConfigBase: types.IdlewatcherConfigBase{
|
||||
IdleTimeout: time.Hour,
|
||||
WakeTimeout: time.Second,
|
||||
},
|
||||
},
|
||||
idleTicker: ticker,
|
||||
readyNotifyCh: make(chan struct{}, 1),
|
||||
events: gevents.NewHistory(),
|
||||
}
|
||||
w.lastReset.Store(time.Now())
|
||||
w.state.Store(&containerState{
|
||||
status: idlewatchertypes.ContainerStatusStopped,
|
||||
})
|
||||
return w
|
||||
}
|
||||
@@ -48,17 +48,10 @@ window.onload = async () => {
|
||||
* @param {string} type - Console line type (e.g. ready, error, or WakeEventType)
|
||||
* @param {string} message
|
||||
* @param {string} timestamp - ISO timestamp string
|
||||
* @param {EventLevel} [level]
|
||||
*/
|
||||
function addConsoleLine(type, message, timestamp, level) {
|
||||
const validLevels = ["debug", "info", "warn", "error"];
|
||||
const lvl = validLevels.includes(level)
|
||||
? level
|
||||
: type === "error"
|
||||
? "error"
|
||||
: "info";
|
||||
function addConsoleLine(type, message, timestamp) {
|
||||
const line = document.createElement("div");
|
||||
line.className = `console-line ${type} level-${lvl}`;
|
||||
line.className = `console-line ${type}`;
|
||||
|
||||
const timestampEl = document.createElement("span");
|
||||
timestampEl.className = "console-timestamp";
|
||||
@@ -120,25 +113,20 @@ window.onload = async () => {
|
||||
ready = true;
|
||||
// Container is ready, hide loading dots and refresh
|
||||
loadingDotsEl.style.display = "none";
|
||||
addConsoleLine(
|
||||
type,
|
||||
"Container is ready, refreshing...",
|
||||
timestamp,
|
||||
evt.level,
|
||||
);
|
||||
addConsoleLine(type, "Container is ready, refreshing...", timestamp);
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 200);
|
||||
} else if (type === "error" || evt.level === "error") {
|
||||
// Show error message and hide loading dots
|
||||
const errorMessage = payload.error || payload.message || "Unknown error";
|
||||
addConsoleLine(type, errorMessage, timestamp, evt.level);
|
||||
addConsoleLine(type, errorMessage, timestamp);
|
||||
loadingDotsEl.style.display = "none";
|
||||
eventSource.close();
|
||||
} else {
|
||||
// Show other message types
|
||||
const message = payload.message || "";
|
||||
addConsoleLine(type, message, timestamp, evt.level);
|
||||
const message = payload.message;
|
||||
addConsoleLine(type, message, timestamp);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
249
internal/idlewatcher/html/loading.min.js
vendored
249
internal/idlewatcher/html/loading.min.js
vendored
@@ -1,249 +0,0 @@
|
||||
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
||||
try {
|
||||
var info = gen[key](arg);
|
||||
var value = info.value;
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
if (info.done) {
|
||||
resolve(value);
|
||||
} else {
|
||||
Promise.resolve(value).then(_next, _throw);
|
||||
}
|
||||
}
|
||||
function _async_to_generator(fn) {
|
||||
return function () {
|
||||
var self = this,
|
||||
args = arguments;
|
||||
return new Promise(function (resolve, reject) {
|
||||
var gen = fn.apply(self, args);
|
||||
function _next(value) {
|
||||
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
|
||||
}
|
||||
function _throw(err) {
|
||||
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
|
||||
}
|
||||
_next(undefined);
|
||||
});
|
||||
};
|
||||
}
|
||||
function _ts_generator(thisArg, body) {
|
||||
var f,
|
||||
y,
|
||||
t,
|
||||
_ = {
|
||||
label: 0,
|
||||
sent: function () {
|
||||
if (t[0] & 1) throw t[1];
|
||||
return t[1];
|
||||
},
|
||||
trys: [],
|
||||
ops: [],
|
||||
},
|
||||
g = Object.create(
|
||||
(typeof Iterator === "function" ? Iterator : Object).prototype,
|
||||
),
|
||||
d = Object.defineProperty;
|
||||
return (
|
||||
d(g, "next", { value: verb(0) }),
|
||||
d(g, "throw", { value: verb(1) }),
|
||||
d(g, "return", { value: verb(2) }),
|
||||
typeof Symbol === "function" &&
|
||||
d(g, Symbol.iterator, {
|
||||
value: function () {
|
||||
return this;
|
||||
},
|
||||
}),
|
||||
g
|
||||
);
|
||||
function verb(n) {
|
||||
return function (v) {
|
||||
return step([n, v]);
|
||||
};
|
||||
}
|
||||
function step(op) {
|
||||
if (f) throw new TypeError("Generator is already executing.");
|
||||
while ((g && ((g = 0), op[0] && (_ = 0)), _))
|
||||
try {
|
||||
if (
|
||||
((f = 1),
|
||||
y &&
|
||||
(t =
|
||||
op[0] & 2
|
||||
? y["return"]
|
||||
: op[0]
|
||||
? y["throw"] || ((t = y["return"]) && t.call(y), 0)
|
||||
: y.next) &&
|
||||
!(t = t.call(y, op[1])).done)
|
||||
)
|
||||
return t;
|
||||
if (((y = 0), t)) op = [op[0] & 2, t.value];
|
||||
switch (op[0]) {
|
||||
case 0:
|
||||
case 1:
|
||||
t = op;
|
||||
break;
|
||||
case 4:
|
||||
_.label++;
|
||||
return { value: op[1], done: false };
|
||||
case 5:
|
||||
_.label++;
|
||||
y = op[1];
|
||||
op = [0];
|
||||
continue;
|
||||
case 7:
|
||||
op = _.ops.pop();
|
||||
_.trys.pop();
|
||||
continue;
|
||||
default:
|
||||
if (
|
||||
!((t = _.trys), (t = t.length > 0 && t[t.length - 1])) &&
|
||||
(op[0] === 6 || op[0] === 2)
|
||||
) {
|
||||
_ = 0;
|
||||
continue;
|
||||
}
|
||||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
|
||||
_.label = op[1];
|
||||
break;
|
||||
}
|
||||
if (op[0] === 6 && _.label < t[1]) {
|
||||
_.label = t[1];
|
||||
t = op;
|
||||
break;
|
||||
}
|
||||
if (t && _.label < t[2]) {
|
||||
_.label = t[2];
|
||||
_.ops.push(op);
|
||||
break;
|
||||
}
|
||||
if (t[2]) _.ops.pop();
|
||||
_.trys.pop();
|
||||
continue;
|
||||
}
|
||||
op = body.call(thisArg, _);
|
||||
} catch (e) {
|
||||
op = [6, e];
|
||||
y = 0;
|
||||
} finally {
|
||||
f = t = 0;
|
||||
}
|
||||
if (op[0] & 5) throw op[1];
|
||||
return { value: op[0] ? op[1] : void 0, done: true };
|
||||
}
|
||||
}
|
||||
var ready = false;
|
||||
window.onload = function () {
|
||||
return _async_to_generator(function () {
|
||||
var consoleEl, loadingDotsEl, eventSource;
|
||||
function formatTimestamp(timestamp) {
|
||||
var date = new Date(timestamp);
|
||||
return date.toLocaleTimeString("en-US", {
|
||||
hour12: false,
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
fractionalSecondDigits: 3,
|
||||
});
|
||||
}
|
||||
function addConsoleLine(type, message, timestamp, level) {
|
||||
var validLevels = ["debug", "info", "warn", "error"];
|
||||
var lvl = validLevels.includes(level)
|
||||
? level
|
||||
: type === "error"
|
||||
? "error"
|
||||
: "info";
|
||||
var line = document.createElement("div");
|
||||
line.className = "console-line ".concat(type, " level-").concat(lvl);
|
||||
var timestampEl = document.createElement("span");
|
||||
timestampEl.className = "console-timestamp";
|
||||
timestampEl.textContent = formatTimestamp(timestamp);
|
||||
var messageEl = document.createElement("span");
|
||||
messageEl.className = "console-message";
|
||||
messageEl.textContent = message;
|
||||
line.appendChild(timestampEl);
|
||||
line.appendChild(messageEl);
|
||||
consoleEl.appendChild(line);
|
||||
consoleEl.scrollTop = consoleEl.scrollHeight;
|
||||
}
|
||||
return _ts_generator(this, function (_state) {
|
||||
consoleEl = document.getElementById("console");
|
||||
loadingDotsEl = document.getElementById("loading-dots");
|
||||
if (!consoleEl || !loadingDotsEl) {
|
||||
console.error("Required DOM elements not found");
|
||||
return [2];
|
||||
}
|
||||
if (typeof wakeEventsPath === "undefined" || !wakeEventsPath) {
|
||||
addConsoleLine(
|
||||
"error",
|
||||
"Configuration error: wakeEventsPath not defined",
|
||||
new Date().toISOString(),
|
||||
);
|
||||
loadingDotsEl.style.display = "none";
|
||||
return [2];
|
||||
}
|
||||
if (typeof EventSource === "undefined") {
|
||||
addConsoleLine(
|
||||
"error",
|
||||
"Browser does not support Server-Sent Events",
|
||||
new Date().toISOString(),
|
||||
);
|
||||
loadingDotsEl.style.display = "none";
|
||||
return [2];
|
||||
}
|
||||
eventSource = new EventSource(wakeEventsPath);
|
||||
eventSource.onmessage = function (event) {
|
||||
var evt;
|
||||
try {
|
||||
evt = JSON.parse(event.data);
|
||||
} catch (unused) {
|
||||
addConsoleLine(
|
||||
"error",
|
||||
"Invalid event data: ".concat(event.data),
|
||||
new Date().toISOString(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
var payload = evt.data || {};
|
||||
var type = evt.action;
|
||||
var timestamp = evt.timestamp;
|
||||
if (type === "ready") {
|
||||
ready = true;
|
||||
loadingDotsEl.style.display = "none";
|
||||
addConsoleLine(
|
||||
type,
|
||||
"Container is ready, refreshing...",
|
||||
timestamp,
|
||||
evt.level,
|
||||
);
|
||||
setTimeout(function () {
|
||||
window.location.reload();
|
||||
}, 200);
|
||||
} else if (type === "error" || evt.level === "error") {
|
||||
var errorMessage =
|
||||
payload.error || payload.message || "Unknown error";
|
||||
addConsoleLine(type, errorMessage, timestamp, evt.level);
|
||||
loadingDotsEl.style.display = "none";
|
||||
eventSource.close();
|
||||
} else {
|
||||
var message = payload.message || "";
|
||||
addConsoleLine(type, message, timestamp, evt.level);
|
||||
}
|
||||
};
|
||||
eventSource.onerror = function () {
|
||||
if (ready) {
|
||||
return;
|
||||
}
|
||||
addConsoleLine(
|
||||
"error",
|
||||
"Connection lost. Please refresh the page.",
|
||||
new Date().toISOString(),
|
||||
);
|
||||
loadingDotsEl.style.display = "none";
|
||||
eventSource.close();
|
||||
};
|
||||
return [2];
|
||||
});
|
||||
})();
|
||||
};
|
||||
@@ -1,12 +1,17 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<title>{{.Title}}</title>
|
||||
<link rel="stylesheet" href="{{.LoadingPageCSSPath}}" />
|
||||
<link rel="icon" href="{{.FavIconPath}}" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500;600&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
@@ -15,8 +20,8 @@
|
||||
<script src="{{.LoadingPageJSPath}}" defer></script>
|
||||
<div class="container">
|
||||
<!-- icon handled by waker_http -->
|
||||
<img class="logo" src="{{.FavIconPath}}" alt="" />
|
||||
<div id="loading-dots" class="loading-dots" aria-hidden="true">
|
||||
<img class="logo" src="{{.FavIconPath}}" />
|
||||
<div id="loading-dots" class="loading-dots">
|
||||
<div class="dot"></div>
|
||||
<div class="dot"></div>
|
||||
<div class="dot"></div>
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<meta charset="UTF-8" /><meta
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1.0"
|
||||
/><meta name="color-scheme" content="light dark" /><title>{{.Title}}</title
|
||||
><link rel="stylesheet" href="{{.LoadingPageCSSPath}}" /><link
|
||||
rel="icon"
|
||||
href="{{.FavIconPath}}"
|
||||
/>
|
||||
<body>
|
||||
<script>
|
||||
const wakeEventsPath = "{{.WakeEventsPath}}";
|
||||
</script>
|
||||
<script src="{{.LoadingPageJSPath}}" defer></script>
|
||||
<div class="container">
|
||||
<img class="logo" src="{{.FavIconPath}}" alt="" />
|
||||
<div id="loading-dots" class="loading-dots" aria-hidden="true">
|
||||
<div class="dot"></div>
|
||||
<div class="dot"></div>
|
||||
<div class="dot"></div>
|
||||
</div>
|
||||
<div id="console" class="console"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,283 +1,194 @@
|
||||
/* size variables */
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--fg: #141413;
|
||||
--fg-secondary: #52514e;
|
||||
--bg: #fafaf9;
|
||||
--surface: #ffffff;
|
||||
--rule: #292826;
|
||||
--border: color-mix(in srgb, var(--rule) 12%, var(--bg));
|
||||
--text: var(--fg);
|
||||
--muted: var(--fg-secondary);
|
||||
--faint: color-mix(in srgb, var(--rule) 9%, transparent);
|
||||
--console-bg: #f5f5f4;
|
||||
--msg-error: #b91c1c;
|
||||
--text-emphasis: color-mix(in srgb, var(--fg) 92%, var(--bg) 8%);
|
||||
--dot-size: 6px;
|
||||
--logo-size: 72px;
|
||||
--radius: 10px;
|
||||
--font-sans:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
Roboto, "Helvetica Neue", sans-serif;
|
||||
--font-mono:
|
||||
ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, monospace;
|
||||
--accent-starting: #52525b;
|
||||
--accent-waking-dep: #0369a1;
|
||||
--accent-dep-ready: #15803d;
|
||||
--accent-container-woke: #047857;
|
||||
--accent-waiting-ready: #b45309;
|
||||
--accent-ready: #166534;
|
||||
--accent-error: #dc2626;
|
||||
--level-debug: color-mix(in srgb, var(--fg-secondary) 18%, transparent);
|
||||
--level-info: transparent;
|
||||
--level-warn: color-mix(in srgb, #b45309 16%, transparent);
|
||||
--level-error: color-mix(in srgb, #dc2626 12%, transparent);
|
||||
--dot-size: 12px;
|
||||
--logo-size: 100px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
--fg: #ecebe8;
|
||||
--fg-secondary: #9c9a94;
|
||||
--bg: #0c0c0b;
|
||||
--surface: #111113;
|
||||
--rule: #5c5954;
|
||||
--border: rgba(255, 255, 255, 0.06);
|
||||
--text: var(--fg);
|
||||
--muted: var(--fg-secondary);
|
||||
--faint: rgba(255, 255, 255, 0.08);
|
||||
--console-bg: #0a0a0b;
|
||||
--msg-error: #fecaca;
|
||||
--text-emphasis: rgba(255, 255, 255, 0.95);
|
||||
--accent-starting: #a1a1aa;
|
||||
--accent-waking-dep: #38bdf8;
|
||||
--accent-dep-ready: #4ade80;
|
||||
--accent-container-woke: #34d399;
|
||||
--accent-waiting-ready: #fbbf24;
|
||||
--accent-ready: #86efac;
|
||||
--accent-error: #f87171;
|
||||
--level-debug: rgba(161, 161, 170, 0.35);
|
||||
--level-info: transparent;
|
||||
--level-warn: rgba(251, 191, 36, 0.2);
|
||||
--level-error: rgba(248, 113, 113, 0.12);
|
||||
}
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
/* Global Styles */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: var(--bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
font-size: 15px;
|
||||
line-height: 1.45;
|
||||
color: var(--text);
|
||||
min-height: 100vh;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: #f8f9fa;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background: linear-gradient(135deg, #121212 0%, #1e1e1e 100%);
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: clamp(1rem, 4vw, 1.5rem);
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
padding: 32px;
|
||||
border-radius: 16px;
|
||||
background-color: rgba(30, 30, 30, 0.9);
|
||||
box-shadow: 0 16px 64px rgba(0, 0, 0, 0.4), 0 4px 16px rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(12px);
|
||||
max-width: 90%;
|
||||
transition: all 0.3s ease;
|
||||
min-width: 600px;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 28px;
|
||||
padding: 28px 24px 24px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: var(--logo-size);
|
||||
height: var(--logo-size);
|
||||
opacity: 0.92;
|
||||
min-width: auto;
|
||||
max-width: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Spinner Styles */
|
||||
.loading-dots {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
height: 10px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: var(--dot-size);
|
||||
height: var(--dot-size);
|
||||
background-color: #66d9ef;
|
||||
border-radius: 50%;
|
||||
background: var(--muted);
|
||||
animation: pulse 1.2s ease-in-out infinite;
|
||||
animation: bounce 1.3s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.dot:nth-child(1) {
|
||||
animation-delay: 0ms;
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
.dot:nth-child(2) {
|
||||
animation-delay: 160ms;
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
.dot:nth-child(3) {
|
||||
animation-delay: 320ms;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
@keyframes bounce {
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
opacity: 0.25;
|
||||
transform: scale(0.92);
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
40% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
|
||||
/* Message Styles */
|
||||
.message {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
text-align: left;
|
||||
color: #f8f9fa;
|
||||
max-width: 100%;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
/* Console Styles */
|
||||
.console {
|
||||
width: 100%;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
letter-spacing: -0.01em;
|
||||
background: var(--console-bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: calc(var(--radius) - 2px);
|
||||
padding: 10px 0;
|
||||
min-height: 180px;
|
||||
max-height: 280px;
|
||||
font-family: "Fira Code", "Consolas", "Monaco", "Courier New", monospace;
|
||||
font-size: 14px;
|
||||
background-color: #1a1a1a;
|
||||
border: 1px solid #333;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
color: #e0e0e0;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--faint) transparent;
|
||||
max-height: 300px;
|
||||
min-height: 200px;
|
||||
width: 100%;
|
||||
box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.console-line {
|
||||
display: grid;
|
||||
grid-template-columns: 5.5rem 1fr;
|
||||
column-gap: 12px;
|
||||
align-items: baseline;
|
||||
padding: 6px 12px 6px 14px;
|
||||
border-inline-start: 2px solid var(--line-accent, var(--accent-starting));
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--level-bg, transparent) 0%,
|
||||
transparent 48px
|
||||
);
|
||||
border-bottom: 1px solid var(--faint);
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 4px;
|
||||
padding: 2px 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.console-line:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.console-line.level-debug {
|
||||
--level-bg: var(--level-debug);
|
||||
}
|
||||
|
||||
.console-line.level-info {
|
||||
--level-bg: var(--level-info);
|
||||
}
|
||||
|
||||
.console-line.level-warn {
|
||||
--level-bg: var(--level-warn);
|
||||
}
|
||||
|
||||
.console-line.level-error {
|
||||
--level-bg: var(--level-error);
|
||||
border-inline-start-width: 3px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.console-timestamp {
|
||||
color: var(--muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
color: #66d9ef;
|
||||
margin-right: 12px;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.console-message {
|
||||
color: var(--text);
|
||||
flex: 1;
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.console-line.starting {
|
||||
--line-accent: var(--accent-starting);
|
||||
.console-line.starting .console-message {
|
||||
color: #f9f871;
|
||||
}
|
||||
|
||||
.console-line.waking_dep {
|
||||
--line-accent: var(--accent-waking-dep);
|
||||
.console-line.waking_dep .console-message {
|
||||
color: #66d9ef;
|
||||
}
|
||||
|
||||
.console-line.dep_ready {
|
||||
--line-accent: var(--accent-dep-ready);
|
||||
.console-line.dep_ready .console-message {
|
||||
color: #a6e22e;
|
||||
}
|
||||
|
||||
.console-line.container_woke {
|
||||
--line-accent: var(--accent-container-woke);
|
||||
.console-line.container_woke .console-message {
|
||||
color: #a6e22e;
|
||||
}
|
||||
|
||||
.console-line.waiting_ready {
|
||||
--line-accent: var(--accent-waiting-ready);
|
||||
}
|
||||
|
||||
.console-line.ready {
|
||||
--line-accent: var(--accent-ready);
|
||||
.console-line.waiting_ready .console-message {
|
||||
color: #fd971f;
|
||||
}
|
||||
|
||||
.console-line.ready .console-message {
|
||||
font-weight: 600;
|
||||
color: var(--text-emphasis);
|
||||
}
|
||||
|
||||
.console-line.error {
|
||||
--line-accent: var(--accent-error);
|
||||
color: #a6e22e;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.console-line.error .console-message {
|
||||
color: var(--msg-error);
|
||||
font-weight: 500;
|
||||
color: #f92672;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Loading dots in console */
|
||||
.console-loading {
|
||||
display: inline-flex;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
margin-inline-start: 6px;
|
||||
vertical-align: middle;
|
||||
gap: 4px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.console-loading-dot {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: #66d9ef;
|
||||
border-radius: 50%;
|
||||
background: var(--muted);
|
||||
animation: pulse 1.2s ease-in-out infinite;
|
||||
animation: bounce 1.3s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.console-loading-dot:nth-child(1) {
|
||||
animation-delay: 0ms;
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
.console-loading-dot:nth-child(2) {
|
||||
animation-delay: 160ms;
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
.console-loading-dot:nth-child(3) {
|
||||
animation-delay: 320ms;
|
||||
|
||||
/* Logo */
|
||||
.logo {
|
||||
width: var(--logo-size);
|
||||
height: var(--logo-size);
|
||||
}
|
||||
|
||||
@@ -18,22 +18,16 @@ type templateData struct {
|
||||
WakeEventsPath string
|
||||
}
|
||||
|
||||
//go:embed html/loading_page.min.html
|
||||
//go:embed html/loading_page.html
|
||||
var loadingPage []byte
|
||||
var loadingPageTmpl = template.Must(template.New("loading_page").Parse(string(loadingPage)))
|
||||
|
||||
//go:embed html/style.css
|
||||
var cssBytes []byte
|
||||
|
||||
//go:embed html/loading.min.js
|
||||
//go:embed html/loading-min.js
|
||||
var jsBytes []byte
|
||||
|
||||
func setNoStoreHeaders(header http.Header) {
|
||||
header.Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0")
|
||||
header.Set("Pragma", "no-cache")
|
||||
header.Set("Expires", "0")
|
||||
}
|
||||
|
||||
func (w *Watcher) writeLoadingPage(rw http.ResponseWriter) error {
|
||||
msg := w.cfg.ContainerName() + " is starting..."
|
||||
|
||||
@@ -46,7 +40,9 @@ func (w *Watcher) writeLoadingPage(rw http.ResponseWriter) error {
|
||||
data.WakeEventsPath = idlewatcher.WakeEventsPath
|
||||
|
||||
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
setNoStoreHeaders(rw.Header())
|
||||
rw.Header().Set("Cache-Control", "no-cache")
|
||||
rw.Header().Add("Cache-Control", "no-store")
|
||||
rw.Header().Add("Cache-Control", "must-revalidate")
|
||||
rw.Header().Add("Connection", "close")
|
||||
err := loadingPageTmpl.Execute(rw, data)
|
||||
return err
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
@@ -51,6 +52,10 @@ func loadNS[T store](ns namespace) T {
|
||||
store := reflect.New(reflect.TypeFor[T]().Elem()).Interface().(T)
|
||||
store.Initialize()
|
||||
|
||||
if common.IsTest {
|
||||
return store
|
||||
}
|
||||
|
||||
path := filepath.Join(storesPath, string(ns)+".json")
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
@@ -61,12 +66,13 @@ func loadNS[T store](ns namespace) T {
|
||||
}
|
||||
} else {
|
||||
defer file.Close()
|
||||
if err := json.NewDecoder(file).Decode(&store); err != nil {
|
||||
if err := sonic.ConfigDefault.NewDecoder(file).Decode(&store); err != nil {
|
||||
log.Err(err).
|
||||
Str("path", path).
|
||||
Msg("failed to load store")
|
||||
}
|
||||
}
|
||||
stores[ns] = store
|
||||
log.Debug().
|
||||
Str("namespace", string(ns)).
|
||||
Str("path", path).
|
||||
@@ -78,7 +84,7 @@ func save() error {
|
||||
errs := gperr.NewBuilder("failed to save data stores")
|
||||
for ns, store := range stores {
|
||||
path := filepath.Join(storesPath, string(ns)+".json")
|
||||
if err := serialization.SaveFile(path, &store, 0o644, json.Marshal); err != nil {
|
||||
if err := serialization.SaveFile(path, &store, 0o644, sonic.Marshal); err != nil {
|
||||
errs.Add(err)
|
||||
}
|
||||
}
|
||||
@@ -108,12 +114,12 @@ func (s *MapStore[VT]) Initialize() {
|
||||
}
|
||||
|
||||
func (s MapStore[VT]) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(xsync.ToPlainMap(s.Map))
|
||||
return sonic.Marshal(xsync.ToPlainMap(s.Map))
|
||||
}
|
||||
|
||||
func (s *MapStore[VT]) UnmarshalJSON(data []byte) error {
|
||||
tmp := make(map[string]VT)
|
||||
if err := json.Unmarshal(data, &tmp); err != nil {
|
||||
if err := sonic.Unmarshal(data, &tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Map = xsync.NewMap[string, VT](xsync.WithPresize(len(tmp)))
|
||||
@@ -129,10 +135,10 @@ func (obj *ObjectStore[Ptr]) Initialize() {
|
||||
}
|
||||
|
||||
func (obj ObjectStore[Ptr]) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(obj.ptr)
|
||||
return sonic.Marshal(obj.ptr)
|
||||
}
|
||||
|
||||
func (obj *ObjectStore[Ptr]) UnmarshalJSON(data []byte) error {
|
||||
obj.Initialize()
|
||||
return json.Unmarshal(data, obj.ptr)
|
||||
return sonic.Unmarshal(data, obj.ptr)
|
||||
}
|
||||
|
||||
@@ -4,18 +4,7 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func setupTest(t *testing.T) {
|
||||
prevStoresPath := storesPath
|
||||
storesPath = t.TempDir()
|
||||
t.Cleanup(func() {
|
||||
storesPath = prevStoresPath
|
||||
clear(stores)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewJSON(t *testing.T) {
|
||||
setupTest(t)
|
||||
|
||||
store := Store[string]("test")
|
||||
store.Store("a", "1")
|
||||
if v, _ := store.Load("a"); v != "1" {
|
||||
@@ -24,8 +13,9 @@ func TestNewJSON(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSaveLoadStore(t *testing.T) {
|
||||
setupTest(t)
|
||||
defer clear(stores)
|
||||
|
||||
storesPath = t.TempDir()
|
||||
store := Store[string]("test")
|
||||
store.Store("a", "1")
|
||||
if err := save(); err != nil {
|
||||
@@ -54,8 +44,9 @@ type testObject struct {
|
||||
func (*testObject) Initialize() {}
|
||||
|
||||
func TestSaveLoadObject(t *testing.T) {
|
||||
setupTest(t)
|
||||
defer clear(stores)
|
||||
|
||||
storesPath = t.TempDir()
|
||||
obj := Object[*testObject]("test")
|
||||
obj.I = 1
|
||||
obj.S = "1"
|
||||
|
||||
36
internal/maxmind/city_cache.go
Normal file
36
internal/maxmind/city_cache.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package maxmind
|
||||
|
||||
import (
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
)
|
||||
|
||||
var cityCache = xsync.NewMap[string, *City]()
|
||||
|
||||
func (cfg *MaxMind) lookupCity(ip *IPInfo) (*City, bool) {
|
||||
if ip.City != nil {
|
||||
return ip.City, true
|
||||
}
|
||||
|
||||
if cfg.db.Reader == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
city, ok := cityCache.Load(ip.Str)
|
||||
if ok {
|
||||
ip.City = city
|
||||
return city, true
|
||||
}
|
||||
|
||||
cfg.db.RLock()
|
||||
defer cfg.db.RUnlock()
|
||||
|
||||
city = new(City)
|
||||
err := cfg.db.Lookup(ip.IP, city)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
cityCache.Store(ip.Str, city)
|
||||
ip.City = city
|
||||
return city, true
|
||||
}
|
||||
@@ -2,24 +2,16 @@ package maxmind
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/notif"
|
||||
"github.com/yusing/goutils/task"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
var instance *MaxMind
|
||||
|
||||
var (
|
||||
warnOnce sync.Once
|
||||
errLogRateLimiter = rate.NewLimiter(rate.Every(3*time.Second), 1)
|
||||
errLogSuppressedCounts = xsync.NewMap[string, *atomic.Int64](xsync.WithPresize(32))
|
||||
)
|
||||
var warnOnce sync.Once
|
||||
|
||||
func warnNotConfigured() {
|
||||
log.Warn().Msg("MaxMind not configured, geo lookup will fail")
|
||||
@@ -45,53 +37,9 @@ func HasInstance() bool {
|
||||
}
|
||||
|
||||
func LookupCity(ip *IPInfo) (*City, bool) {
|
||||
if ip.City != nil {
|
||||
return ip.City, false
|
||||
}
|
||||
|
||||
if instance == nil {
|
||||
warnOnce.Do(warnNotConfigured)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
city, err := lookupCityNoContext(ip.Str)
|
||||
if err != nil {
|
||||
logLookupCityError(ip.Str, err)
|
||||
return nil, false
|
||||
}
|
||||
ip.City = city
|
||||
return city, true
|
||||
}
|
||||
|
||||
func lookupCityErrorKey(err error) string {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
func incrementSuppressedLookupCityError(err error) {
|
||||
key := lookupCityErrorKey(err)
|
||||
counter, _ := errLogSuppressedCounts.LoadOrCompute(key, func() (*atomic.Int64, bool) {
|
||||
return &atomic.Int64{}, false
|
||||
})
|
||||
counter.Add(1)
|
||||
}
|
||||
|
||||
func flushSuppressedLookupCityError(err error) int64 {
|
||||
counter, ok := errLogSuppressedCounts.Load(lookupCityErrorKey(err))
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return counter.Swap(0)
|
||||
}
|
||||
|
||||
func logLookupCityError(ipStr string, err error) {
|
||||
if !errLogRateLimiter.Allow() {
|
||||
incrementSuppressedLookupCityError(err)
|
||||
return
|
||||
}
|
||||
|
||||
event := log.Err(err).Str("ip", ipStr)
|
||||
if suppressedCount := flushSuppressedLookupCityError(err); suppressedCount > 0 {
|
||||
event = event.Int64("suppressed_count", suppressedCount)
|
||||
}
|
||||
event.Msg("failed to lookup city")
|
||||
return instance.lookupCity(ip)
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package maxmind
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/yusing/goutils/cache"
|
||||
)
|
||||
|
||||
var ErrInvalidIP = errors.New("invalid IP address")
|
||||
var ErrDBNotLoaded = errors.New("maxmind database not loaded")
|
||||
|
||||
var lookupCityFn = cache.NewKeyFunc(func(_ context.Context, ipStr string) (*City, error) {
|
||||
return instance.lookupCityReal(ipStr)
|
||||
}).WithMaxEntries(1000).Build()
|
||||
|
||||
func lookupCityNoContext(ipStr string) (*City, error) {
|
||||
return lookupCityFn(context.Background(), ipStr)
|
||||
}
|
||||
|
||||
func (cfg *MaxMind) lookupCityReal(ipStr string) (*City, error) {
|
||||
cfg.db.RLock()
|
||||
defer cfg.db.RUnlock()
|
||||
|
||||
if cfg.db.Reader == nil {
|
||||
return nil, ErrDBNotLoaded
|
||||
}
|
||||
|
||||
city := new(City)
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return nil, ErrInvalidIP
|
||||
}
|
||||
err := cfg.db.Lookup(ip, city)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return city, nil
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package maxmind
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
func TestLookupCityPropagatesContext(t *testing.T) {
|
||||
oldLookupCityFn := lookupCityFn
|
||||
t.Cleanup(func() {
|
||||
lookupCityFn = oldLookupCityFn
|
||||
})
|
||||
|
||||
type contextKey string
|
||||
const wantIP = "1.1.1.1"
|
||||
const wantValue contextKey = "trace-id"
|
||||
|
||||
lookupCityFn = func(ctx context.Context, ipStr string) (*City, error) {
|
||||
require.Equal(t, wantIP, ipStr)
|
||||
require.Equal(t, "trace-123", ctx.Value(wantValue))
|
||||
return &City{}, nil
|
||||
}
|
||||
|
||||
ctx := context.WithValue(t.Context(), wantValue, "trace-123")
|
||||
city, err := lookupCityFn(ctx, wantIP)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, city)
|
||||
}
|
||||
|
||||
func TestLookupCityRealReturnsErrDBNotLoaded(t *testing.T) {
|
||||
cfg := &MaxMind{}
|
||||
|
||||
city, err := cfg.lookupCityReal("1.1.1.1")
|
||||
require.ErrorIs(t, err, ErrDBNotLoaded)
|
||||
assert.Nil(t, city)
|
||||
}
|
||||
|
||||
func TestLogLookupCityErrorIncludesSuppressedCount(t *testing.T) {
|
||||
oldLimiter := errLogRateLimiter
|
||||
oldSuppressed := errLogSuppressedCounts
|
||||
oldLogger := log.Logger
|
||||
t.Cleanup(func() {
|
||||
errLogRateLimiter = oldLimiter
|
||||
errLogSuppressedCounts = oldSuppressed
|
||||
log.Logger = oldLogger
|
||||
})
|
||||
|
||||
errLogRateLimiter = rate.NewLimiter(rate.Every(24*time.Hour), 1)
|
||||
errLogSuppressedCounts = xsync.NewMap[string, *atomic.Int64]()
|
||||
|
||||
var buf bytes.Buffer
|
||||
log.Logger = zerolog.New(&buf)
|
||||
|
||||
err := errors.New("boom")
|
||||
logLookupCityError("1.1.1.1", err)
|
||||
logLookupCityError("1.1.1.1", err)
|
||||
|
||||
errLogRateLimiter = rate.NewLimiter(rate.Inf, 1)
|
||||
logLookupCityError("1.1.1.1", err)
|
||||
|
||||
lines := bytes.Split(bytes.TrimSpace(buf.Bytes()), []byte("\n"))
|
||||
require.Len(t, lines, 2)
|
||||
|
||||
var entry map[string]any
|
||||
require.NoError(t, json.Unmarshal(lines[1], &entry))
|
||||
assert.Equal(t, "failed to lookup city", entry["message"])
|
||||
assert.Equal(t, float64(1), entry["suppressed_count"])
|
||||
|
||||
buf.Reset()
|
||||
logLookupCityError("1.1.1.1", err)
|
||||
entry = map[string]any{}
|
||||
require.NoError(t, json.Unmarshal(bytes.TrimSpace(buf.Bytes()), &entry))
|
||||
_, hasSuppressedCount := entry["suppressed_count"]
|
||||
assert.False(t, hasSuppressedCount)
|
||||
}
|
||||
@@ -106,10 +106,8 @@ func (cfg *MaxMind) LoadMaxMindDB(parent task.Parent) error {
|
||||
} else {
|
||||
cfg.Logger().Info().Msg("MaxMind DB loaded")
|
||||
cfg.db.Reader = reader
|
||||
if !common.IsTest {
|
||||
go cfg.scheduleUpdate(parent)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user