mirror of
https://github.com/juanfont/headscale.git
synced 2026-10-25 15:56:49 +09:00
hi: consolidate doctor and stats helpers
This commit is contained in:
1 parent
722a2f1e18
commit
ea5e52f662
5 files changed
+168
-297
No files matched your search
+30
-41
@@ -71,26 +71,8 @@ func killTestContainers(ctx context.Context) error {
|
|||||||
removed := 0
|
removed := 0
|
||||||
|
|
||||||
for _, cont := range containers {
|
for _, cont := range containers {
|
||||||
shouldRemove := false
|
if isTestContainerName(cont.Names) {
|
||||||
|
if killAndRemove(ctx, cli, cont) {
|
||||||
for _, name := range cont.Names {
|
|
||||||
if strings.Contains(name, "headscale-test-suite") ||
|
|
||||||
strings.Contains(name, "hs-") ||
|
|
||||||
strings.Contains(name, "ts-") ||
|
|
||||||
strings.Contains(name, "derp-") {
|
|
||||||
shouldRemove = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if shouldRemove {
|
|
||||||
// First kill the container if it's running
|
|
||||||
if cont.State == "running" {
|
|
||||||
_ = cli.ContainerKill(ctx, cont.ID, "KILL")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then remove the container with retry logic
|
|
||||||
if removeContainerWithRetry(ctx, cli, cont.ID) {
|
|
||||||
removed++
|
removed++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,13 +111,7 @@ func killTestContainersByRunID(ctx context.Context, runID string) error {
|
|||||||
removed := 0
|
removed := 0
|
||||||
|
|
||||||
for _, cont := range containers {
|
for _, cont := range containers {
|
||||||
// Kill the container if it's running
|
if killAndRemove(ctx, cli, cont) {
|
||||||
if cont.State == "running" {
|
|
||||||
_ = cli.ContainerKill(ctx, cont.ID, "KILL")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the container with retry logic
|
|
||||||
if removeContainerWithRetry(ctx, cli, cont.ID) {
|
|
||||||
removed++
|
removed++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,20 +149,8 @@ func cleanupStaleTestContainers(ctx context.Context) error {
|
|||||||
|
|
||||||
for _, cont := range containers {
|
for _, cont := range containers {
|
||||||
// Only remove containers that look like test containers
|
// Only remove containers that look like test containers
|
||||||
shouldRemove := false
|
if isTestContainerName(cont.Names) {
|
||||||
|
if killAndRemove(ctx, cli, cont) {
|
||||||
for _, name := range cont.Names {
|
|
||||||
if strings.Contains(name, "headscale-test-suite") ||
|
|
||||||
strings.Contains(name, "hs-") ||
|
|
||||||
strings.Contains(name, "ts-") ||
|
|
||||||
strings.Contains(name, "derp-") {
|
|
||||||
shouldRemove = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if shouldRemove {
|
|
||||||
if removeContainerWithRetry(ctx, cli, cont.ID) {
|
|
||||||
removed++
|
removed++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,6 +187,31 @@ func removeContainerWithRetry(ctx context.Context, cli *client.Client, container
|
|||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isTestContainerName reports whether any of the container names belong to an
|
||||||
|
// integration test container.
|
||||||
|
func isTestContainerName(names []string) bool {
|
||||||
|
for _, name := range names {
|
||||||
|
if strings.Contains(name, "headscale-test-suite") ||
|
||||||
|
strings.Contains(name, "hs-") ||
|
||||||
|
strings.Contains(name, "ts-") ||
|
||||||
|
strings.Contains(name, "derp-") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// killAndRemove kills a running container then removes it with retry logic,
|
||||||
|
// reporting whether the removal succeeded.
|
||||||
|
func killAndRemove(ctx context.Context, cli *client.Client, cont container.Summary) bool {
|
||||||
|
if cont.State == "running" {
|
||||||
|
_ = cli.ContainerKill(ctx, cont.ID, "KILL")
|
||||||
|
}
|
||||||
|
|
||||||
|
return removeContainerWithRetry(ctx, cli, cont.ID)
|
||||||
|
}
|
||||||
|
|
||||||
// pruneDockerNetworks removes unused Docker networks.
|
// pruneDockerNetworks removes unused Docker networks.
|
||||||
func pruneDockerNetworks(ctx context.Context) error {
|
func pruneDockerNetworks(ctx context.Context) error {
|
||||||
cli, err := createDockerClient(ctx)
|
cli, err := createDockerClient(ctx)
|
||||||
|
|||||||
+109
-182
@@ -33,6 +33,21 @@ type DoctorResult struct {
|
|||||||
Suggestions []string
|
Suggestions []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pass builds a passing DoctorResult.
|
||||||
|
func pass(name, message string) DoctorResult {
|
||||||
|
return DoctorResult{Name: name, Status: statusPass, Message: message}
|
||||||
|
}
|
||||||
|
|
||||||
|
// warn builds a warning DoctorResult with optional suggestions.
|
||||||
|
func warn(name, message string, suggestions ...string) DoctorResult {
|
||||||
|
return DoctorResult{Name: name, Status: statusWarn, Message: message, Suggestions: suggestions}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fail builds a failing DoctorResult with optional suggestions.
|
||||||
|
func fail(name, message string, suggestions ...string) DoctorResult {
|
||||||
|
return DoctorResult{Name: name, Status: statusFail, Message: message, Suggestions: suggestions}
|
||||||
|
}
|
||||||
|
|
||||||
// runDoctorCheck performs comprehensive pre-flight checks for integration testing.
|
// runDoctorCheck performs comprehensive pre-flight checks for integration testing.
|
||||||
func runDoctorCheck(ctx context.Context) error {
|
func runDoctorCheck(ctx context.Context) error {
|
||||||
results := []DoctorResult{}
|
results := []DoctorResult{}
|
||||||
@@ -80,129 +95,91 @@ func runDoctorCheck(ctx context.Context) error {
|
|||||||
func checkDockerBinary() DoctorResult {
|
func checkDockerBinary() DoctorResult {
|
||||||
_, err := exec.LookPath("docker")
|
_, err := exec.LookPath("docker")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DoctorResult{
|
return fail(
|
||||||
Name: "Docker Binary",
|
"Docker Binary",
|
||||||
Status: statusFail,
|
"Docker binary not found in PATH",
|
||||||
Message: "Docker binary not found in PATH",
|
"Install Docker: https://docs.docker.com/get-docker/",
|
||||||
Suggestions: []string{
|
"For macOS: consider using colima or Docker Desktop",
|
||||||
"Install Docker: https://docs.docker.com/get-docker/",
|
"Ensure docker is in your PATH",
|
||||||
"For macOS: consider using colima or Docker Desktop",
|
)
|
||||||
"Ensure docker is in your PATH",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DoctorResult{
|
return pass("Docker Binary", "Docker binary found")
|
||||||
Name: "Docker Binary",
|
|
||||||
Status: statusPass,
|
|
||||||
Message: "Docker binary found",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkDockerDaemon verifies Docker daemon is running and accessible.
|
// checkDockerDaemon verifies Docker daemon is running and accessible.
|
||||||
func checkDockerDaemon(ctx context.Context) DoctorResult {
|
func checkDockerDaemon(ctx context.Context) DoctorResult {
|
||||||
cli, err := createDockerClient(ctx)
|
cli, err := createDockerClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DoctorResult{
|
return fail(
|
||||||
Name: nameDockerDaemon,
|
nameDockerDaemon,
|
||||||
Status: statusFail,
|
fmt.Sprintf("Cannot create Docker client: %v", err),
|
||||||
Message: fmt.Sprintf("Cannot create Docker client: %v", err),
|
"Start Docker daemon/service",
|
||||||
Suggestions: []string{
|
"Check Docker Desktop is running (if using Docker Desktop)",
|
||||||
"Start Docker daemon/service",
|
"For colima: run 'colima start'",
|
||||||
"Check Docker Desktop is running (if using Docker Desktop)",
|
"Verify DOCKER_HOST environment variable if set",
|
||||||
"For colima: run 'colima start'",
|
)
|
||||||
"Verify DOCKER_HOST environment variable if set",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
defer cli.Close()
|
defer cli.Close()
|
||||||
|
|
||||||
_, err = cli.Ping(ctx)
|
_, err = cli.Ping(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DoctorResult{
|
return fail(
|
||||||
Name: nameDockerDaemon,
|
nameDockerDaemon,
|
||||||
Status: statusFail,
|
fmt.Sprintf("Cannot ping Docker daemon: %v", err),
|
||||||
Message: fmt.Sprintf("Cannot ping Docker daemon: %v", err),
|
"Ensure Docker daemon is running",
|
||||||
Suggestions: []string{
|
"Check Docker socket permissions",
|
||||||
"Ensure Docker daemon is running",
|
"Try: docker info",
|
||||||
"Check Docker socket permissions",
|
)
|
||||||
"Try: docker info",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DoctorResult{
|
return pass(nameDockerDaemon, "Docker daemon is running and accessible")
|
||||||
Name: nameDockerDaemon,
|
|
||||||
Status: statusPass,
|
|
||||||
Message: "Docker daemon is running and accessible",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkDockerContext verifies Docker context configuration.
|
// checkDockerContext verifies Docker context configuration.
|
||||||
func checkDockerContext(ctx context.Context) DoctorResult {
|
func checkDockerContext(ctx context.Context) DoctorResult {
|
||||||
contextInfo, err := getCurrentDockerContext(ctx)
|
contextInfo, err := getCurrentDockerContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DoctorResult{
|
return warn(
|
||||||
Name: nameDockerContext,
|
nameDockerContext,
|
||||||
Status: statusWarn,
|
"Could not detect Docker context, using default settings",
|
||||||
Message: "Could not detect Docker context, using default settings",
|
"Check: docker context ls",
|
||||||
Suggestions: []string{
|
"Consider setting up a specific context if needed",
|
||||||
"Check: docker context ls",
|
)
|
||||||
"Consider setting up a specific context if needed",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if contextInfo == nil {
|
if contextInfo == nil {
|
||||||
return DoctorResult{
|
return pass(nameDockerContext, "Using default Docker context")
|
||||||
Name: nameDockerContext,
|
|
||||||
Status: statusPass,
|
|
||||||
Message: "Using default Docker context",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DoctorResult{
|
return pass(nameDockerContext, "Using Docker context: "+contextInfo.Name)
|
||||||
Name: nameDockerContext,
|
|
||||||
Status: statusPass,
|
|
||||||
Message: "Using Docker context: " + contextInfo.Name,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkDockerSocket verifies Docker socket accessibility.
|
// checkDockerSocket verifies Docker socket accessibility.
|
||||||
func checkDockerSocket(ctx context.Context) DoctorResult {
|
func checkDockerSocket(ctx context.Context) DoctorResult {
|
||||||
cli, err := createDockerClient(ctx)
|
cli, err := createDockerClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DoctorResult{
|
return fail(
|
||||||
Name: nameDockerSocket,
|
nameDockerSocket,
|
||||||
Status: statusFail,
|
fmt.Sprintf("Cannot access Docker socket: %v", err),
|
||||||
Message: fmt.Sprintf("Cannot access Docker socket: %v", err),
|
"Check Docker socket permissions",
|
||||||
Suggestions: []string{
|
"Add user to docker group: sudo usermod -aG docker $USER",
|
||||||
"Check Docker socket permissions",
|
"For colima: ensure socket is accessible",
|
||||||
"Add user to docker group: sudo usermod -aG docker $USER",
|
)
|
||||||
"For colima: ensure socket is accessible",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
defer cli.Close()
|
defer cli.Close()
|
||||||
|
|
||||||
info, err := cli.Info(ctx)
|
info, err := cli.Info(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DoctorResult{
|
return fail(
|
||||||
Name: nameDockerSocket,
|
nameDockerSocket,
|
||||||
Status: statusFail,
|
fmt.Sprintf("Cannot get Docker info: %v", err),
|
||||||
Message: fmt.Sprintf("Cannot get Docker info: %v", err),
|
"Check Docker daemon status",
|
||||||
Suggestions: []string{
|
"Verify socket permissions",
|
||||||
"Check Docker daemon status",
|
)
|
||||||
"Verify socket permissions",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DoctorResult{
|
return pass(nameDockerSocket, fmt.Sprintf("Docker socket accessible (Server: %s)", info.ServerVersion))
|
||||||
Name: nameDockerSocket,
|
|
||||||
Status: statusPass,
|
|
||||||
Message: fmt.Sprintf("Docker socket accessible (Server: %s)", info.ServerVersion),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkDockerHubCredentials warns when pulls would be anonymous and
|
// checkDockerHubCredentials warns when pulls would be anonymous and
|
||||||
@@ -210,34 +187,23 @@ func checkDockerSocket(ctx context.Context) DoctorResult {
|
|||||||
func checkDockerHubCredentials() DoctorResult {
|
func checkDockerHubCredentials() DoctorResult {
|
||||||
_, _, source := dockertestutil.Credentials()
|
_, _, source := dockertestutil.Credentials()
|
||||||
if source == dockertestutil.CredentialSourceAnonymous {
|
if source == dockertestutil.CredentialSourceAnonymous {
|
||||||
return DoctorResult{
|
return warn(
|
||||||
Name: "Docker Hub Credentials",
|
"Docker Hub Credentials",
|
||||||
Status: "WARN",
|
"No Docker Hub credentials found — pulls will be rate-limited (100/6h per IP)",
|
||||||
Message: "No Docker Hub credentials found — pulls will be rate-limited (100/6h per IP)",
|
"Run: docker login",
|
||||||
Suggestions: []string{
|
"Or export DOCKERHUB_USERNAME and DOCKERHUB_TOKEN",
|
||||||
"Run: docker login",
|
"In CI: ensure the docker/login-action step is configured with secrets",
|
||||||
"Or export DOCKERHUB_USERNAME and DOCKERHUB_TOKEN",
|
)
|
||||||
"In CI: ensure the docker/login-action step is configured with secrets",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DoctorResult{
|
return pass("Docker Hub Credentials", fmt.Sprintf("Credentials available (source: %s)", source))
|
||||||
Name: "Docker Hub Credentials",
|
|
||||||
Status: "PASS",
|
|
||||||
Message: fmt.Sprintf("Credentials available (source: %s)", source),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkGolangImage verifies the golang Docker image is available locally or can be pulled.
|
// checkGolangImage verifies the golang Docker image is available locally or can be pulled.
|
||||||
func checkGolangImage(ctx context.Context) DoctorResult {
|
func checkGolangImage(ctx context.Context) DoctorResult {
|
||||||
cli, err := createDockerClient(ctx)
|
cli, err := createDockerClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DoctorResult{
|
return fail(nameGolangImage, "Cannot create Docker client for image check")
|
||||||
Name: nameGolangImage,
|
|
||||||
Status: statusFail,
|
|
||||||
Message: "Cannot create Docker client for image check",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
defer cli.Close()
|
defer cli.Close()
|
||||||
|
|
||||||
@@ -247,81 +213,56 @@ func checkGolangImage(ctx context.Context) DoctorResult {
|
|||||||
// First check if image is available locally
|
// First check if image is available locally
|
||||||
available, err := checkImageAvailableLocally(ctx, cli, imageName)
|
available, err := checkImageAvailableLocally(ctx, cli, imageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DoctorResult{
|
return fail(
|
||||||
Name: nameGolangImage,
|
nameGolangImage,
|
||||||
Status: statusFail,
|
fmt.Sprintf("Cannot check golang image %s: %v", imageName, err),
|
||||||
Message: fmt.Sprintf("Cannot check golang image %s: %v", imageName, err),
|
"Check Docker daemon status",
|
||||||
Suggestions: []string{
|
"Try: docker images | grep golang",
|
||||||
"Check Docker daemon status",
|
)
|
||||||
"Try: docker images | grep golang",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if available {
|
if available {
|
||||||
return DoctorResult{
|
return pass(nameGolangImage, fmt.Sprintf("Golang image %s is available locally", imageName))
|
||||||
Name: nameGolangImage,
|
|
||||||
Status: statusPass,
|
|
||||||
Message: fmt.Sprintf("Golang image %s is available locally", imageName),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Image not available locally, try to pull it
|
// Image not available locally, try to pull it
|
||||||
err = ensureImageAvailable(ctx, cli, imageName, false)
|
err = ensureImageAvailable(ctx, cli, imageName, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DoctorResult{
|
return fail(
|
||||||
Name: nameGolangImage,
|
nameGolangImage,
|
||||||
Status: statusFail,
|
fmt.Sprintf("Golang image %s not available locally and cannot pull: %v", imageName, err),
|
||||||
Message: fmt.Sprintf("Golang image %s not available locally and cannot pull: %v", imageName, err),
|
"Check internet connectivity",
|
||||||
Suggestions: []string{
|
"Verify Docker Hub access",
|
||||||
"Check internet connectivity",
|
"Try: docker pull "+imageName,
|
||||||
"Verify Docker Hub access",
|
"Or run tests offline if image was pulled previously",
|
||||||
"Try: docker pull " + imageName,
|
)
|
||||||
"Or run tests offline if image was pulled previously",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DoctorResult{
|
return pass(nameGolangImage, fmt.Sprintf("Golang image %s is now available", imageName))
|
||||||
Name: nameGolangImage,
|
|
||||||
Status: statusPass,
|
|
||||||
Message: fmt.Sprintf("Golang image %s is now available", imageName),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkGoInstallation verifies Go is installed and working.
|
// checkGoInstallation verifies Go is installed and working.
|
||||||
func checkGoInstallation(ctx context.Context) DoctorResult {
|
func checkGoInstallation(ctx context.Context) DoctorResult {
|
||||||
_, err := exec.LookPath("go")
|
_, err := exec.LookPath("go")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DoctorResult{
|
return fail(
|
||||||
Name: nameGoInstall,
|
nameGoInstall,
|
||||||
Status: statusFail,
|
"Go binary not found in PATH",
|
||||||
Message: "Go binary not found in PATH",
|
"Install Go: https://golang.org/dl/",
|
||||||
Suggestions: []string{
|
"Ensure go is in your PATH",
|
||||||
"Install Go: https://golang.org/dl/",
|
)
|
||||||
"Ensure go is in your PATH",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, "go", "version")
|
cmd := exec.CommandContext(ctx, "go", "version")
|
||||||
|
|
||||||
output, err := cmd.Output()
|
output, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DoctorResult{
|
return fail(nameGoInstall, fmt.Sprintf("Cannot get Go version: %v", err))
|
||||||
Name: nameGoInstall,
|
|
||||||
Status: statusFail,
|
|
||||||
Message: fmt.Sprintf("Cannot get Go version: %v", err),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
version := strings.TrimSpace(string(output))
|
version := strings.TrimSpace(string(output))
|
||||||
|
|
||||||
return DoctorResult{
|
return pass(nameGoInstall, version)
|
||||||
Name: nameGoInstall,
|
|
||||||
Status: statusPass,
|
|
||||||
Message: version,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkGitRepository verifies we're in a git repository.
|
// checkGitRepository verifies we're in a git repository.
|
||||||
@@ -330,22 +271,15 @@ func checkGitRepository(ctx context.Context) DoctorResult {
|
|||||||
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DoctorResult{
|
return fail(
|
||||||
Name: "Git Repository",
|
"Git Repository",
|
||||||
Status: statusFail,
|
"Not in a Git repository",
|
||||||
Message: "Not in a Git repository",
|
"Run from within the headscale git repository",
|
||||||
Suggestions: []string{
|
"Clone the repository: git clone https://github.com/juanfont/headscale.git",
|
||||||
"Run from within the headscale git repository",
|
)
|
||||||
"Clone the repository: git clone https://github.com/juanfont/headscale.git",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DoctorResult{
|
return pass("Git Repository", "Running in Git repository")
|
||||||
Name: "Git Repository",
|
|
||||||
Status: statusPass,
|
|
||||||
Message: "Running in Git repository",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkRequiredFiles verifies required files exist.
|
// checkRequiredFiles verifies required files exist.
|
||||||
@@ -368,23 +302,16 @@ func checkRequiredFiles(ctx context.Context) DoctorResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(missingFiles) > 0 {
|
if len(missingFiles) > 0 {
|
||||||
return DoctorResult{
|
return fail(
|
||||||
Name: "Required Files",
|
"Required Files",
|
||||||
Status: statusFail,
|
"Missing required files: "+strings.Join(missingFiles, ", "),
|
||||||
Message: "Missing required files: " + strings.Join(missingFiles, ", "),
|
"Ensure you're in the headscale project root directory",
|
||||||
Suggestions: []string{
|
"Check that integration/ directory exists",
|
||||||
"Ensure you're in the headscale project root directory",
|
"Verify this is a complete headscale repository",
|
||||||
"Check that integration/ directory exists",
|
)
|
||||||
"Verify this is a complete headscale repository",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DoctorResult{
|
return pass("Required Files", "All required files found")
|
||||||
Name: "Required Files",
|
|
||||||
Status: statusPass,
|
|
||||||
Message: "All required files found",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// displayDoctorResults shows the results in a formatted way.
|
// displayDoctorResults shows the results in a formatted way.
|
||||||
|
|||||||
+11
-14
@@ -86,20 +86,17 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cleanAll(ctx context.Context) error {
|
func cleanAll(ctx context.Context) error {
|
||||||
err := killTestContainers(ctx)
|
for _, step := range []func(context.Context) error{
|
||||||
if err != nil {
|
killTestContainers,
|
||||||
return err
|
pruneDockerNetworks,
|
||||||
|
cleanOldImages,
|
||||||
|
cleanCacheVolume,
|
||||||
|
} {
|
||||||
|
err := step(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pruneDockerNetworks(ctx)
|
return nil
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cleanOldImages(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return cleanCacheVolume(ctx)
|
|
||||||
}
|
}
|
||||||
+10
-52
@@ -6,6 +6,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/creachadair/command"
|
"github.com/creachadair/command"
|
||||||
@@ -66,64 +67,21 @@ func runIntegrationTest(env *command.Env) error {
|
|||||||
|
|
||||||
// detectGoVersion reads the Go version from go.mod file.
|
// detectGoVersion reads the Go version from go.mod file.
|
||||||
func detectGoVersion() string {
|
func detectGoVersion() string {
|
||||||
goModPath := filepath.Join("..", "..", "go.mod")
|
content, err := os.ReadFile("go.mod")
|
||||||
|
|
||||||
if _, err := os.Stat("go.mod"); err == nil { //nolint:noinlineerr
|
|
||||||
goModPath = "go.mod"
|
|
||||||
} else if _, err := os.Stat("../../go.mod"); err == nil { //nolint:noinlineerr
|
|
||||||
goModPath = "../../go.mod"
|
|
||||||
}
|
|
||||||
|
|
||||||
content, err := os.ReadFile(goModPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "1.26.1"
|
content, err = os.ReadFile(filepath.Join("..", "..", "go.mod"))
|
||||||
|
if err != nil {
|
||||||
|
return "1.26.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lines := splitLines(string(content))
|
for line := range strings.Lines(string(content)) {
|
||||||
for _, line := range lines {
|
if rest, ok := strings.CutPrefix(line, "go "); ok {
|
||||||
if len(line) > 3 && line[:3] == "go " {
|
if f := strings.Fields(rest); len(f) > 0 {
|
||||||
version := line[3:]
|
return f[0]
|
||||||
if idx := indexOf(version, " "); idx != -1 {
|
|
||||||
version = version[:idx]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return version
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "1.26.1"
|
return "1.26.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
// splitLines splits a string into lines without using [strings.Split].
|
|
||||||
func splitLines(s string) []string {
|
|
||||||
var (
|
|
||||||
lines []string
|
|
||||||
current string
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, char := range s {
|
|
||||||
if char == '\n' {
|
|
||||||
lines = append(lines, current)
|
|
||||||
current = ""
|
|
||||||
} else {
|
|
||||||
current += string(char)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if current != "" {
|
|
||||||
lines = append(lines, current)
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
// indexOf finds the first occurrence of substr in s.
|
|
||||||
func indexOf(s, substr string) int {
|
|
||||||
for i := 0; i <= len(s)-len(substr); i++ {
|
|
||||||
if s[i:i+len(substr)] == substr {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
+8
-8
@@ -374,17 +374,17 @@ func (sc *StatsCollector) GetSummary() []ContainerStatsSummary {
|
|||||||
SampleCount: len(stats),
|
SampleCount: len(stats),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate CPU stats
|
extract := func(get func(StatsSample) float64) []float64 {
|
||||||
cpuValues := make([]float64, len(stats))
|
values := make([]float64, len(stats))
|
||||||
memoryValues := make([]float64, len(stats))
|
for i, sample := range stats {
|
||||||
|
values[i] = get(sample)
|
||||||
|
}
|
||||||
|
|
||||||
for i, sample := range stats {
|
return values
|
||||||
cpuValues[i] = sample.CPUUsage
|
|
||||||
memoryValues[i] = sample.MemoryMB
|
|
||||||
}
|
}
|
||||||
|
|
||||||
summary.CPU = calculateStatsSummary(cpuValues)
|
summary.CPU = calculateStatsSummary(extract(func(s StatsSample) float64 { return s.CPUUsage }))
|
||||||
summary.Memory = calculateStatsSummary(memoryValues)
|
summary.Memory = calculateStatsSummary(extract(func(s StatsSample) float64 { return s.MemoryMB }))
|
||||||
|
|
||||||
summaries = append(summaries, summary)
|
summaries = append(summaries, summary)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user