From bd02934014dfc35edbb4ee790ad1371fc01bde05 Mon Sep 17 00:00:00 2001 From: HelpMe Date: Tue, 16 Jun 2026 01:56:23 +0200 Subject: [PATCH 1/2] Add Windows dev setup and path fixes --- README.md | 51 +++++++++++-- .../core/services/coldstarter.go | 2 +- apps/druid/adapters/cli/client/dev.go | 6 +- internal/core/domain/scroll.go | 2 +- internal/core/services/coldstarter.go | 2 +- internal/core/services/registry/oci_test.go | 5 ++ internal/runtime/docker/storage_test.go | 11 +-- internal/utils/fs_test.go | 4 ++ scripts/build-windows.ps1 | 65 +++++++++++++++++ scripts/k3d-build-pull-image.ps1 | 42 +++++++++++ scripts/start-kubernetes-daemon.ps1 | 71 +++++++++++++++++++ 11 files changed, 248 insertions(+), 13 deletions(-) create mode 100644 scripts/build-windows.ps1 create mode 100644 scripts/k3d-build-pull-image.ps1 create mode 100644 scripts/start-kubernetes-daemon.ps1 diff --git a/README.md b/README.md index eea87aad..e3c0340e 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ A good use case is to let it run inside of a docker container. It will give addi The current runtime backends are Docker for local development and Kubernetes for in-cluster or kubeconfig-backed cluster operation. -## Installation +## Installation and Local Build -We publish [releases on Github](https://github.com/highcard-dev/druid-cli/releases). +We publish [releases on GitHub](https://github.com/highcard-dev/druid-cli/releases). -You can easlily install druid-cli on Linux by running: +You can install the latest Linux release with: ```bash curl -L -o druid "https://github.com/highcard-dev/druid-cli/releases/latest/download/druid" && sudo install -c -m 0755 druid /usr/local/bin @@ -18,6 +18,28 @@ curl -L -o druid "https://github.com/highcard-dev/druid-cli/releases/latest/down Also consider our installation documentation: [https://docs.druid.gg/cli/introduction](https://docs.druid.gg/cli/introduction) +### Windows development build + +For local Windows development, use the sibling monorepo tool installer first: + +```powershell +cd ..\monorepo +powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\install-windows-tools.ps1 +``` + +Then build both CLI binaries from this repository: + +```powershell +cd ..\druid-cli +powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\build-windows.ps1 +.\bin\druid.exe version +``` + +The Windows build script generates OpenAPI clients, then writes: + +- `bin/druid.exe` +- `bin/druid-coldstarter.exe` + ## Scroll OCI manifest The Druid CLI uses a **so called Scroll** to describe container-backed commands. @@ -39,6 +61,12 @@ Build all binaries with: make build ``` +On Windows without GNU Make, use: + +```powershell +.\scripts\build-windows.ps1 +``` + Common local flow: ```bash @@ -55,7 +83,7 @@ For examples, omit `[name]` so each scroll derives its own id from `scroll.yaml` ### Dependency based command runner -The way commands are handled is described in the `scroll.yaml` and is similar to how Github Actions work, with support for long-running container commands. +The way commands are handled is described in the `scroll.yaml` and is similar to how GitHub Actions work, with support for long-running container commands. Commands can also depend on each other. ### Web Server @@ -69,6 +97,21 @@ Runtime selection is daemon-only: start the daemon with `druid daemon --runtime Kubernetes runtime support is available with `druid daemon --runtime kubernetes` for in-cluster daemons or out-of-cluster daemons using kubeconfig. It stores daemon scroll state in ConfigMaps, materializes OCI artifacts through `druid worker pull` Jobs, and uses kubelet pod stats for procedure-level traffic checks. See `docs/kubernetes_runtime.md` for kubeconfig, RBAC, and PVC setup. +For the local K3D cluster created by the monorepo, build and import the runtime image, then start the daemon: + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\k3d-build-pull-image.ps1 +powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\start-kubernetes-daemon.ps1 +``` + +The daemon listens on: + +- management API: `http://127.0.0.1:8081` +- public API: `http://127.0.0.1:8082` + +Those defaults match the monorepo's generated local operator and SPA environment files. +The Windows daemon script automatically uses `..\monorepo\.tools\kubeconfig-druid-gs.yaml` when no kubeconfig is set. + ## Documentation Read more at https://docs.druid.gg/cli diff --git a/apps/druid-coldstarter/core/services/coldstarter.go b/apps/druid-coldstarter/core/services/coldstarter.go index 54645bca..45bd4878 100644 --- a/apps/druid-coldstarter/core/services/coldstarter.go +++ b/apps/druid-coldstarter/core/services/coldstarter.go @@ -96,7 +96,7 @@ func portServiceFromEnv(root string) (*envPortService, error) { } if handler != "generic" { path := filepath.Join(root, filepath.Clean(handler)) - if rel, err := filepath.Rel(root, path); err != nil || rel == ".." || filepath.IsAbs(rel) || strings.HasPrefix(rel, "../") { + if rel, err := filepath.Rel(root, path); err != nil || rel == ".." || filepath.IsAbs(rel) || strings.HasPrefix(filepath.ToSlash(rel), "../") { return nil, fmt.Errorf("%s must be generic or a path below DRUID_ROOT", key) } } diff --git a/apps/druid/adapters/cli/client/dev.go b/apps/druid/adapters/cli/client/dev.go index e1d8a83d..83e326be 100644 --- a/apps/druid/adapters/cli/client/dev.go +++ b/apps/druid/adapters/cli/client/dev.go @@ -337,12 +337,14 @@ func (s devServer) writeFile(c *fiber.Ctx, raw string) error { func devFilePath(root string, raw string) (string, error) { cleaned := filepath.Clean(strings.TrimPrefix(raw, "/")) - if cleaned == "." || cleaned == ".." || strings.HasPrefix(cleaned, "../") { + cleanedSlash := filepath.ToSlash(cleaned) + if cleanedSlash == "." || cleanedSlash == ".." || strings.HasPrefix(cleanedSlash, "../") { return "", fmt.Errorf("invalid path %q", raw) } full := filepath.Join(root, filepath.FromSlash(cleaned)) rel, err := filepath.Rel(root, full) - if err != nil || rel == ".." || strings.HasPrefix(rel, "../") { + relSlash := filepath.ToSlash(rel) + if err != nil || relSlash == ".." || strings.HasPrefix(relSlash, "../") { return "", fmt.Errorf("invalid path %q", raw) } return full, nil diff --git a/internal/core/domain/scroll.go b/internal/core/domain/scroll.go index 85f36b32..681397a0 100644 --- a/internal/core/domain/scroll.go +++ b/internal/core/domain/scroll.go @@ -363,7 +363,7 @@ func (sc *Scroll) Validate(strict bool) error { } } //scan for files in sc.scrollDir - if sc.scrollDir == "" { + if sc.scrollDir == "" || strings.Contains(sc.scrollDir, "://") { return nil } entries, err := os.ReadDir(sc.scrollDir) diff --git a/internal/core/services/coldstarter.go b/internal/core/services/coldstarter.go index 6b51ac9c..aa9f8478 100644 --- a/internal/core/services/coldstarter.go +++ b/internal/core/services/coldstarter.go @@ -78,7 +78,7 @@ func (c *ColdStarter) Serve(ctx context.Context) { handler = lua.NewGenericReturnHandler() } else { path := filepath.Join(c.dir, filepath.Clean(port.ColdstarterHandler)) - if rel, err := filepath.Rel(c.dir, path); err != nil || rel == ".." || filepath.IsAbs(rel) || strings.HasPrefix(rel, "../") { + if rel, err := filepath.Rel(c.dir, path); err != nil || rel == ".." || filepath.IsAbs(rel) || strings.HasPrefix(filepath.ToSlash(rel), "../") { logger.Log().Error("Invalid coldstarter handler path", zap.String("handler", port.ColdstarterHandler)) continue } diff --git a/internal/core/services/registry/oci_test.go b/internal/core/services/registry/oci_test.go index 782a3ef6..fbe24c95 100644 --- a/internal/core/services/registry/oci_test.go +++ b/internal/core/services/registry/oci_test.go @@ -6,6 +6,7 @@ import ( "net/http/httptest" "os" "path/filepath" + "runtime" "strings" "testing" @@ -211,6 +212,10 @@ func TestResolveAnnotationInfoReadsManifestAnnotations(t *testing.T) { } func TestPushPullExecutableDataChunkPreservesMode(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Windows filesystems do not preserve POSIX executable bits") + } + tmpDir := t.TempDir() t.Chdir(tmpDir) diff --git a/internal/runtime/docker/storage_test.go b/internal/runtime/docker/storage_test.go index 4cad7f6e..d000adeb 100644 --- a/internal/runtime/docker/storage_test.go +++ b/internal/runtime/docker/storage_test.go @@ -40,10 +40,12 @@ func TestRuntimeRootRefUsesBindRoot(t *testing.T) { } func TestParseRootRefSupportsVolumeBindAndLocalBindPath(t *testing.T) { + bindRoot := filepath.Join(t.TempDir(), "scroll") + bindRef := "docker-bind://" + bindRoot cases := map[string]RootRef{ "docker-volume://druid-scroll-data": {Kind: StorageVolume, Source: "druid-scroll-data"}, - "docker-bind:///tmp/druid/scroll": {Kind: StorageBind, Source: "/tmp/druid/scroll"}, - "/tmp/druid/local": {Kind: StorageBind, Source: "/tmp/druid/local"}, + bindRef: {Kind: StorageBind, Source: filepath.Clean(bindRoot)}, + bindRoot: {Kind: StorageBind, Source: filepath.Clean(bindRoot)}, } for input, want := range cases { got, err := ParseRootRef(input) @@ -74,13 +76,14 @@ func TestDockerMountUsesVolumeSubpath(t *testing.T) { } func TestDockerMountUsesBindSubpath(t *testing.T) { - got, err := DockerMount("docker-bind:///tmp/druid/scroll", "/site", false, "data/site") + bindRoot := filepath.Join(t.TempDir(), "scroll") + got, err := DockerMount("docker-bind://"+bindRoot, "/site", false, "data/site") if err != nil { t.Fatal(err) } want := mount.Mount{ Type: mount.TypeBind, - Source: "/tmp/druid/scroll/data/site", + Source: filepath.Join(bindRoot, "data", "site"), Target: "/site", BindOptions: &mount.BindOptions{CreateMountpoint: true}, } diff --git a/internal/utils/fs_test.go b/internal/utils/fs_test.go index 2d75b2db..741f9cc3 100644 --- a/internal/utils/fs_test.go +++ b/internal/utils/fs_test.go @@ -4,6 +4,7 @@ import ( "os" "path/filepath" "reflect" + "runtime" "strings" "testing" @@ -168,6 +169,9 @@ func TestAutoChunkDataDirValidatesSymlinksAfterExpansion(t *testing.T) { contentDir := filepath.Join(dataDir, "serverfiles", "ShooterGame", "Content") mkdirAll(t, filepath.Join(contentDir, "Maps", "TheIsland")) if err := os.Symlink(filepath.Join("Maps", "TheIsland"), filepath.Join(contentDir, "CurrentMap")); err != nil { + if runtime.GOOS == "windows" { + t.Skipf("Windows denied symlink creation: %v", err) + } t.Fatalf("failed to create symlink: %v", err) } diff --git a/scripts/build-windows.ps1 b/scripts/build-windows.ps1 new file mode 100644 index 00000000..0633bb62 --- /dev/null +++ b/scripts/build-windows.ps1 @@ -0,0 +1,65 @@ +[CmdletBinding()] +param( + [string]$Version = 'dev', + [string]$GoBin = '' +) + +$ErrorActionPreference = 'Stop' + +$repoRoot = Split-Path -Parent $PSScriptRoot +$monorepoToolEnv = Join-Path (Split-Path -Parent $repoRoot) 'monorepo\.tools\env.ps1' +if (Test-Path $monorepoToolEnv) { + . $monorepoToolEnv +} + +function Assert-Command { + param([string]$Name) + if (!(Get-Command $Name -ErrorAction SilentlyContinue)) { + throw "$Name is required. Install Go first, or load the monorepo local tools with: . ..\monorepo\.tools\env.ps1" + } +} + +function Invoke-Logged { + param( + [string]$Command, + [string[]]$Arguments + ) + + Write-Host "> $Command $($Arguments -join ' ')" + & $Command @Arguments + if ($LASTEXITCODE -ne 0) { + throw "$Command exited with $LASTEXITCODE" + } +} + +Assert-Command go + +if ([string]::IsNullOrWhiteSpace($GoBin)) { + $goPath = (& go env GOPATH).Trim() + $GoBin = Join-Path $goPath 'bin' +} + +New-Item -ItemType Directory -Force -Path $GoBin, 'bin' | Out-Null +$env:Path = "$GoBin;$env:Path" + +if (!(Get-Command oapi-codegen -ErrorAction SilentlyContinue)) { + Write-Host 'Installing oapi-codegen v2.5.1...' + $env:GOBIN = $GoBin + Invoke-Logged go @('install', 'github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.5.1') +} + +Write-Host 'Generating API clients...' +Invoke-Logged oapi-codegen @('-config', 'api/oapi-codegen.yaml', 'api/openapi.yaml') +Invoke-Logged oapi-codegen @('-config', 'api/dev-oapi-codegen.yaml', 'api/dev.openapi.yaml') +Invoke-Logged oapi-codegen @('-config', 'api/callback-oapi-codegen.yaml', 'api/callback.openapi.yaml') + +$env:CGO_ENABLED = '0' +$ldflags = "-X github.com/highcard-dev/daemon/internal.Version=$Version" + +Write-Host 'Building bin/druid.exe...' +Invoke-Logged go @('build', '-buildvcs=false', '-ldflags', $ldflags, '-o', './bin/druid.exe', './apps/druid') + +Write-Host 'Building bin/druid-coldstarter.exe...' +Invoke-Logged go @('build', '-buildvcs=false', '-ldflags', $ldflags, '-o', './bin/druid-coldstarter.exe', './apps/druid-coldstarter') + +Write-Host 'Druid CLI Windows binaries built in ./bin' diff --git a/scripts/k3d-build-pull-image.ps1 b/scripts/k3d-build-pull-image.ps1 new file mode 100644 index 00000000..19b53f06 --- /dev/null +++ b/scripts/k3d-build-pull-image.ps1 @@ -0,0 +1,42 @@ +[CmdletBinding()] +param( + [string]$Version = 'dev', + [string]$Image = 'druid:local', + [string]$Cluster = 'druid-gs' +) + +$ErrorActionPreference = 'Stop' + +$repoRoot = Split-Path -Parent $PSScriptRoot +$monorepoToolEnv = Join-Path (Split-Path -Parent $repoRoot) 'monorepo\.tools\env.ps1' +if (Test-Path $monorepoToolEnv) { + . $monorepoToolEnv +} + +if ($IsWindows -or $env:OS -eq 'Windows_NT') { + if ([string]::IsNullOrWhiteSpace($env:DOCKER_HOST)) { + $env:DOCKER_HOST = 'npipe:////./pipe/dockerDesktopLinuxEngine' + } +} + +function Invoke-Logged { + param( + [string]$Command, + [string[]]$Arguments + ) + + Write-Host "> $Command $($Arguments -join ' ')" + & $Command @Arguments + if ($LASTEXITCODE -ne 0) { + throw "$Command exited with $LASTEXITCODE" + } +} + +Invoke-Logged docker @('build', '.', '-f', 'Dockerfile', '--build-arg', "VERSION=$Version", '-t', $Image) + +$previousErrorActionPreference = $ErrorActionPreference +$ErrorActionPreference = 'Continue' +docker rm -f "k3d-$Cluster-tools" *> $null +$ErrorActionPreference = $previousErrorActionPreference + +Invoke-Logged k3d @('image', 'import', $Image, '-c', $Cluster) diff --git a/scripts/start-kubernetes-daemon.ps1 b/scripts/start-kubernetes-daemon.ps1 new file mode 100644 index 00000000..2cb3b458 --- /dev/null +++ b/scripts/start-kubernetes-daemon.ps1 @@ -0,0 +1,71 @@ +[CmdletBinding()] +param( + [string]$DruidExe = '.\bin\druid.exe', + [string]$PullImage = 'druid:local', + [string]$Kubeconfig = '', + [string]$ManagementListen = '127.0.0.1:8081', + [string]$PublicListen = '127.0.0.1:8082' +) + +$ErrorActionPreference = 'Stop' + +$repoRoot = Split-Path -Parent $PSScriptRoot +$monorepoRoot = Join-Path (Split-Path -Parent $repoRoot) 'monorepo' +$monorepoToolEnv = Join-Path $monorepoRoot '.tools\env.ps1' +if (Test-Path $monorepoToolEnv) { + . $monorepoToolEnv +} + +if (!(Test-Path $DruidExe)) { + throw "Druid executable not found at $DruidExe. Run .\scripts\build-windows.ps1 first." +} + +function Get-EnvOrDefault { + param( + [string]$Name, + [string]$Default + ) + + $value = [Environment]::GetEnvironmentVariable($Name) + if ([string]::IsNullOrEmpty($value)) { + return $Default + } + return $value +} + +$localKubeconfig = Join-Path $monorepoRoot '.tools\kubeconfig-druid-gs.yaml' +if ([string]::IsNullOrWhiteSpace($Kubeconfig)) { + $Kubeconfig = Get-EnvOrDefault 'DRUID_K8S_KUBECONFIG' (Get-EnvOrDefault 'KUBECONFIG' '') +} +if ([string]::IsNullOrWhiteSpace($Kubeconfig) -and (Test-Path $localKubeconfig)) { + $Kubeconfig = $localKubeconfig +} +if (![string]::IsNullOrWhiteSpace($Kubeconfig)) { + $Kubeconfig = (Resolve-Path $Kubeconfig).Path + $env:KUBECONFIG = $Kubeconfig +} + +$args = @( + 'daemon', + '--runtime', 'kubernetes', + '--listen', $ManagementListen, + '--public-listen', $PublicListen, + '--unsafe-allow-unauthenticated-management', + '--unsafe-allow-unauthenticated-public', + '--worker-daemon-url', "http://$ManagementListen", + '--k8s-pull-image', $PullImage +) + +if (![string]::IsNullOrWhiteSpace($Kubeconfig)) { + $args += @('--k8s-kubeconfig', $Kubeconfig) +} + +$args += @( + '--k8s-ui-s3-bucket', (Get-EnvOrDefault 'DRUID_K8S_UI_S3_BUCKET' 'druid-ui'), + '--k8s-ui-s3-public-base-url', (Get-EnvOrDefault 'DRUID_K8S_UI_S3_PUBLIC_BASE_URL' 'http://127.0.0.1:9000/druid-ui'), + '--k8s-ui-s3-region', (Get-EnvOrDefault 'DRUID_K8S_UI_S3_REGION' 'us-east-1'), + '--k8s-ui-s3-endpoint', (Get-EnvOrDefault 'DRUID_K8S_UI_S3_ENDPOINT' 'http://host.docker.internal:9000'), + '--k8s-ui-s3-credentials-secret', (Get-EnvOrDefault 'DRUID_K8S_UI_S3_CREDENTIALS_SECRET' 'druid-ui-s3') +) + +& $DruidExe @args From 0da310c3b67651e6e02cc2483d9f2f23a85c80e6 Mon Sep 17 00:00:00 2001 From: HelpMe Date: Tue, 16 Jun 2026 03:11:23 +0200 Subject: [PATCH 2/2] Fix Windows K3D daemon callbacks --- README.md | 3 +- internal/core/domain/scroll.go | 49 ++++++++++++++++++++++++++--- internal/core/domain/scroll_test.go | 45 ++++++++++++++++++++++++++ scripts/start-kubernetes-daemon.ps1 | 11 +++++-- 4 files changed, 99 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e3c0340e..26cb7078 100644 --- a/README.md +++ b/README.md @@ -106,8 +106,9 @@ powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\start-kubernetes-d The daemon listens on: -- management API: `http://127.0.0.1:8081` +- management API: `http://127.0.0.1:8081` from the host and `http://host.docker.internal:8081` from K3D worker pods - public API: `http://127.0.0.1:8082` +- worker callback API: `http://host.docker.internal:8083` from K3D worker pods Those defaults match the monorepo's generated local operator and SPA environment files. The Windows daemon script automatically uses `..\monorepo\.tools\kubeconfig-druid-gs.yaml` when no kubeconfig is set. diff --git a/internal/core/domain/scroll.go b/internal/core/domain/scroll.go index 681397a0..81653cf8 100644 --- a/internal/core/domain/scroll.go +++ b/internal/core/domain/scroll.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "os" + "path" "path/filepath" "regexp" "strconv" @@ -311,7 +312,7 @@ func (sc *Scroll) Validate(strict bool) error { if mount.Path == "" { return fmt.Errorf("mount path is required") } - if !filepath.IsAbs(mount.Path) { + if !mountPathIsAbsolute(mount.Path) { return fmt.Errorf("mount path %s must be absolute", mount.Path) } if mountPaths[mount.Path] { @@ -321,11 +322,12 @@ func (sc *Scroll) Validate(strict bool) error { if mount.SubPath == "" { continue } - if filepath.IsAbs(mount.SubPath) { + normalizedSubPath := normalizeMountSubPath(mount.SubPath) + if mountSubPathIsAbsolute(mount.SubPath, normalizedSubPath) { return fmt.Errorf("mount sub_path %s must be relative", mount.SubPath) } - clean := filepath.Clean(mount.SubPath) - if clean == ".." || strings.HasPrefix(clean, "../") { + clean := path.Clean(normalizedSubPath) + if clean == ".." || strings.HasPrefix(clean, "../") || mountSubPathHasParentSegment(normalizedSubPath) { return fmt.Errorf("mount sub_path %s escapes runtime root", mount.SubPath) } } @@ -363,7 +365,7 @@ func (sc *Scroll) Validate(strict bool) error { } } //scan for files in sc.scrollDir - if sc.scrollDir == "" || strings.Contains(sc.scrollDir, "://") { + if sc.scrollDir == "" || isVirtualScrollDir(sc.scrollDir) { return nil } entries, err := os.ReadDir(sc.scrollDir) @@ -393,6 +395,43 @@ func (sc *Scroll) Validate(strict bool) error { return nil } +func normalizeMountSubPath(subPath string) string { + return strings.ReplaceAll(subPath, "\\", "/") +} + +func mountPathIsAbsolute(mountPath string) bool { + normalized := strings.ReplaceAll(mountPath, "\\", "/") + return path.IsAbs(normalized) && !isWindowsDrivePath(normalized) +} + +func mountSubPathIsAbsolute(subPath string, normalized string) bool { + return filepath.IsAbs(subPath) || + path.IsAbs(normalized) || + isWindowsDrivePath(normalized) +} + +func mountSubPathHasParentSegment(subPath string) bool { + for _, segment := range strings.Split(subPath, "/") { + if segment == ".." { + return true + } + } + return false +} + +func isWindowsDrivePath(value string) bool { + return len(value) >= 2 && value[1] == ':' +} + +func isVirtualScrollDir(scrollDir string) bool { + for _, prefix := range []string{"runtime://", "k8s://", "docker-volume://", "docker-bind://"} { + if strings.HasPrefix(scrollDir, prefix) { + return true + } + } + return false +} + func (sc *Scroll) CanColdStart() bool { return len(sc.Ports) != 0 } diff --git a/internal/core/domain/scroll_test.go b/internal/core/domain/scroll_test.go index a34b736e..c4c076aa 100644 --- a/internal/core/domain/scroll_test.go +++ b/internal/core/domain/scroll_test.go @@ -100,6 +100,51 @@ func TestScrollValidateRejectsUnknownServeCommand(t *testing.T) { } } +func TestScrollValidateAcceptsPosixContainerMountPathOnWindows(t *testing.T) { + scroll := testScroll(t, &Procedure{ + Image: "alpine:3.20", + Command: []string{"true"}, + Mounts: []Mount{{ + Path: "/data", + SubPath: "public", + }}, + }) + + if err := scroll.Validate(false); err != nil { + t.Fatalf("Validate() error = %v", err) + } +} + +func TestScrollValidateRejectsEscapingMountSubPath(t *testing.T) { + tests := []string{ + "../private", + `..\private`, + `data\..\private`, + "C:/private", + } + + for _, subPath := range tests { + t.Run(subPath, func(t *testing.T) { + scroll := testScroll(t, &Procedure{ + Image: "alpine:3.20", + Command: []string{"true"}, + Mounts: []Mount{{ + Path: "/data", + SubPath: subPath, + }}, + }) + + err := scroll.Validate(false) + if err == nil { + t.Fatal("Validate() error = nil, want error") + } + if !strings.Contains(err.Error(), "mount sub_path") { + t.Fatalf("Validate() error = %q, want mount sub_path error", err.Error()) + } + }) + } +} + func testScroll(t *testing.T, procedure *Procedure) *Scroll { t.Helper() version, err := semver.NewVersion("0.1.0") diff --git a/scripts/start-kubernetes-daemon.ps1 b/scripts/start-kubernetes-daemon.ps1 index 2cb3b458..d2ccbb07 100644 --- a/scripts/start-kubernetes-daemon.ps1 +++ b/scripts/start-kubernetes-daemon.ps1 @@ -3,8 +3,11 @@ param( [string]$DruidExe = '.\bin\druid.exe', [string]$PullImage = 'druid:local', [string]$Kubeconfig = '', - [string]$ManagementListen = '127.0.0.1:8081', - [string]$PublicListen = '127.0.0.1:8082' + [string]$ManagementListen = '0.0.0.0:8081', + [string]$PublicListen = '127.0.0.1:8082', + [string]$WorkerCallbackListen = '0.0.0.0:8083', + [string]$WorkerCallbackUrl = 'http://host.docker.internal:8083', + [string]$WorkerDaemonUrl = 'http://host.docker.internal:8081' ) $ErrorActionPreference = 'Stop' @@ -52,7 +55,9 @@ $args = @( '--public-listen', $PublicListen, '--unsafe-allow-unauthenticated-management', '--unsafe-allow-unauthenticated-public', - '--worker-daemon-url', "http://$ManagementListen", + '--worker-callback-listen', $WorkerCallbackListen, + '--worker-callback-url', $WorkerCallbackUrl, + '--worker-daemon-url', $WorkerDaemonUrl, '--k8s-pull-image', $PullImage )