Skip to content

Commit cd6b737

Browse files
committed
mvp
1 parent f028895 commit cd6b737

4 files changed

Lines changed: 167 additions & 30 deletions

File tree

internal/config/config.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ type FileConfig struct {
1616
Backend BackendConfig `yaml:"backend"`
1717
}
1818

19-
// BackendConfig contains the backend selection.
19+
// BackendConfig contains the backend selection. At most one backend may be configured.
2020
type BackendConfig struct {
2121
Docker *DockerConfig `yaml:"docker"`
22+
Direct *DirectConfig `yaml:"direct"`
2223
}
2324

2425
// DockerConfig holds Docker-backend-specific configuration.
@@ -27,6 +28,15 @@ type DockerConfig struct {
2728
Environment []EnvEntry `yaml:"environment"`
2829
}
2930

31+
// DirectConfig holds direct-backend-specific configuration.
32+
type DirectConfig struct {
33+
WorkspaceRoot string `yaml:"workspace_root"`
34+
OzPath string `yaml:"oz_path"`
35+
SetupCommand string `yaml:"setup_command"`
36+
TeardownCommand string `yaml:"teardown_command"`
37+
Environment []EnvEntry `yaml:"environment"`
38+
}
39+
3040
// EnvEntry represents a single environment variable in the config file.
3141
// If Value is nil, the variable is inherited from the host process environment.
3242
type EnvEntry struct {
@@ -56,12 +66,22 @@ func Load(path string) (*FileConfig, error) {
5666
}
5767

5868
func (c *FileConfig) validate() error {
69+
if c.Backend.Docker != nil && c.Backend.Direct != nil {
70+
return fmt.Errorf("only one backend (docker or direct) may be configured")
71+
}
72+
5973
if c.Backend.Docker != nil {
6074
if err := validateEnvEntries(c.Backend.Docker.Environment); err != nil {
6175
return fmt.Errorf("docker.environment: %w", err)
6276
}
6377
}
6478

79+
if c.Backend.Direct != nil {
80+
if err := validateEnvEntries(c.Backend.Direct.Environment); err != nil {
81+
return fmt.Errorf("direct.environment: %w", err)
82+
}
83+
}
84+
6585
return nil
6686
}
6787

internal/config/config_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,59 @@ func TestResolveEnvMissingHostVar(t *testing.T) {
197197
}
198198
}
199199

200+
func TestLoadValidDirectConfig(t *testing.T) {
201+
path := writeTestConfig(t, `
202+
worker_id: "direct-worker"
203+
backend:
204+
direct:
205+
workspace_root: "/tmp/oz-workspaces"
206+
setup_command: "/opt/setup.sh"
207+
teardown_command: "/opt/teardown.sh"
208+
environment:
209+
- name: MY_VAR
210+
value: "hello"
211+
`)
212+
213+
cfg, err := Load(path)
214+
if err != nil {
215+
t.Fatalf("unexpected error: %v", err)
216+
}
217+
218+
if cfg.Backend.Direct == nil {
219+
t.Fatal("expected direct backend to be set")
220+
}
221+
if cfg.Backend.Docker != nil {
222+
t.Error("docker backend should be nil")
223+
}
224+
if cfg.Backend.Direct.WorkspaceRoot != "/tmp/oz-workspaces" {
225+
t.Errorf("workspace_root = %q, want %q", cfg.Backend.Direct.WorkspaceRoot, "/tmp/oz-workspaces")
226+
}
227+
if cfg.Backend.Direct.SetupCommand != "/opt/setup.sh" {
228+
t.Errorf("setup_command = %q, want %q", cfg.Backend.Direct.SetupCommand, "/opt/setup.sh")
229+
}
230+
if cfg.Backend.Direct.TeardownCommand != "/opt/teardown.sh" {
231+
t.Errorf("teardown_command = %q, want %q", cfg.Backend.Direct.TeardownCommand, "/opt/teardown.sh")
232+
}
233+
if len(cfg.Backend.Direct.Environment) != 1 {
234+
t.Errorf("environment count = %d, want 1", len(cfg.Backend.Direct.Environment))
235+
}
236+
}
237+
238+
func TestLoadBothBackendsError(t *testing.T) {
239+
path := writeTestConfig(t, `
240+
backend:
241+
docker:
242+
volumes: []
243+
direct:
244+
workspace_root: "/tmp"
245+
`)
246+
247+
_, err := Load(path)
248+
if err == nil {
249+
t.Fatal("expected error when both backends are set")
250+
}
251+
}
252+
200253
func TestLoadFileNotFound(t *testing.T) {
201254
_, err := Load("/nonexistent/path/config.yaml")
202255
if err == nil {

internal/worker/worker.go

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@ type Config struct {
3030
WebSocketURL string
3131
ServerRootURL string
3232
LogLevel string
33-
NoCleanup bool
34-
Volumes []string
35-
Env map[string]string
33+
BackendType string // "docker" or "direct"
34+
35+
// Backend-specific configs. Only the one matching BackendType should be set.
36+
Docker *DockerBackendConfig
37+
Direct *DirectBackendConfig
3638
}
3739

3840
type Worker struct {
@@ -52,11 +54,26 @@ type Worker struct {
5254
func New(ctx context.Context, config Config) (*Worker, error) {
5355
workerCtx, cancel := context.WithCancel(ctx)
5456

55-
backend, err := NewDockerBackend(ctx, DockerBackendConfig{
56-
NoCleanup: config.NoCleanup,
57-
Volumes: config.Volumes,
58-
Env: config.Env,
59-
})
57+
var backend Backend
58+
var err error
59+
60+
switch config.BackendType {
61+
case "direct":
62+
if config.Direct == nil {
63+
cancel()
64+
return nil, fmt.Errorf("direct backend selected but no direct config provided")
65+
}
66+
backend, err = NewDirectBackend(ctx, *config.Direct)
67+
case "docker", "":
68+
if config.Docker == nil {
69+
config.Docker = &DockerBackendConfig{}
70+
}
71+
backend, err = NewDockerBackend(ctx, *config.Docker)
72+
default:
73+
cancel()
74+
return nil, fmt.Errorf("unknown backend type: %q", config.BackendType)
75+
}
76+
6077
if err != nil {
6178
cancel()
6279
return nil, err

main.go

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
var CLI struct {
1818
ConfigFile string `help:"Path to YAML config file" type:"path"`
19+
Backend string `help:"Backend type (docker or direct)" enum:"docker,direct," default:""`
1920
APIKey string `help:"API key for authentication" env:"WARP_API_KEY" required:""`
2021
WorkerID string `help:"Worker host identifier (required via flag or config file)"`
2122
WebSocketURL string `default:"wss://oz.warp.dev/api/v1/selfhosted/worker/ws" hidden:""`
@@ -93,6 +94,16 @@ func mergeConfig(fileConfig *config.FileConfig) (worker.Config, error) {
9394
return worker.Config{}, fmt.Errorf("invalid worker-id: values starting with 'warp' are reserved and cannot be used")
9495
}
9596

97+
// Resolve backend type: CLI --backend > config file backend key > default "docker".
98+
backendType := CLI.Backend
99+
if backendType == "" && fileConfig != nil {
100+
if fileConfig.Backend.Direct != nil {
101+
backendType = "direct"
102+
} else if fileConfig.Backend.Docker != nil {
103+
backendType = "docker"
104+
}
105+
}
106+
96107
// Merge cleanup: --no-cleanup flag > config file cleanup > default (cleanup=true).
97108
noCleanup := CLI.NoCleanup
98109
if !noCleanup && fileConfig != nil && fileConfig.Cleanup != nil {
@@ -105,32 +116,68 @@ func mergeConfig(fileConfig *config.FileConfig) (worker.Config, error) {
105116
return worker.Config{}, err
106117
}
107118

108-
// Merge env: config file first, then CLI entries overlay (CLI wins on key conflict).
109-
mergedEnv := make(map[string]string)
110-
if fileConfig != nil && fileConfig.Backend.Docker != nil {
111-
mergedEnv = config.ResolveEnv(fileConfig.Backend.Docker.Environment)
112-
}
113-
for k, v := range cliEnv {
114-
mergedEnv[k] = v
115-
}
116-
117-
// Merge volumes: config file + CLI (concatenated).
118-
var volumes []string
119-
if fileConfig != nil && fileConfig.Backend.Docker != nil {
120-
volumes = append(volumes, fileConfig.Backend.Docker.Volumes...)
121-
}
122-
volumes = append(volumes, CLI.Volumes...)
123-
124-
return worker.Config{
119+
wc := worker.Config{
125120
APIKey: CLI.APIKey,
126121
WorkerID: workerID,
127122
WebSocketURL: CLI.WebSocketURL,
128123
ServerRootURL: CLI.ServerRootURL,
129124
LogLevel: CLI.LogLevel,
130-
NoCleanup: noCleanup,
131-
Volumes: volumes,
132-
Env: mergedEnv,
133-
}, nil
125+
BackendType: backendType,
126+
}
127+
128+
switch backendType {
129+
case "direct":
130+
// Merge env: config file first, then CLI overlay.
131+
mergedEnv := make(map[string]string)
132+
if fileConfig != nil && fileConfig.Backend.Direct != nil {
133+
mergedEnv = config.ResolveEnv(fileConfig.Backend.Direct.Environment)
134+
}
135+
for k, v := range cliEnv {
136+
mergedEnv[k] = v
137+
}
138+
139+
var workspaceRoot, ozPath, setupCmd, teardownCmd string
140+
if fileConfig != nil && fileConfig.Backend.Direct != nil {
141+
workspaceRoot = fileConfig.Backend.Direct.WorkspaceRoot
142+
ozPath = fileConfig.Backend.Direct.OzPath
143+
setupCmd = fileConfig.Backend.Direct.SetupCommand
144+
teardownCmd = fileConfig.Backend.Direct.TeardownCommand
145+
}
146+
147+
wc.Direct = &worker.DirectBackendConfig{
148+
WorkspaceRoot: workspaceRoot,
149+
OzPath: ozPath,
150+
SetupCommand: setupCmd,
151+
TeardownCommand: teardownCmd,
152+
NoCleanup: noCleanup,
153+
Env: mergedEnv,
154+
}
155+
156+
default: // docker
157+
// Merge env: config file first, then CLI overlay (CLI wins on key conflict).
158+
mergedEnv := make(map[string]string)
159+
if fileConfig != nil && fileConfig.Backend.Docker != nil {
160+
mergedEnv = config.ResolveEnv(fileConfig.Backend.Docker.Environment)
161+
}
162+
for k, v := range cliEnv {
163+
mergedEnv[k] = v
164+
}
165+
166+
// Merge volumes: config file + CLI (concatenated).
167+
var volumes []string
168+
if fileConfig != nil && fileConfig.Backend.Docker != nil {
169+
volumes = append(volumes, fileConfig.Backend.Docker.Volumes...)
170+
}
171+
volumes = append(volumes, CLI.Volumes...)
172+
173+
wc.Docker = &worker.DockerBackendConfig{
174+
NoCleanup: noCleanup,
175+
Volumes: volumes,
176+
Env: mergedEnv,
177+
}
178+
}
179+
180+
return wc, nil
134181
}
135182

136183
// parseEnvFlags parses -e/--env flag values into a map.

0 commit comments

Comments
 (0)