@@ -34,6 +34,7 @@ func hostBaseEnv() []string {
3434// DirectBackendConfig holds configuration specific to the direct (non-containerized) backend.
3535type DirectBackendConfig struct {
3636 WorkspaceRoot string
37+ TargetDir string // If set, run all tasks in this directory instead of creating per-task workspaces.
3738 OzPath string // Path to the oz CLI binary. If empty, looks up "oz" in PATH.
3839 SetupCommand string
3940 TeardownCommand string
@@ -59,13 +60,25 @@ func NewDirectBackend(ctx context.Context, config DirectBackendConfig) (*DirectB
5960 }
6061 log .Infof (ctx , "Using oz CLI at: %s" , ozPath )
6162
62- if config .WorkspaceRoot == "" {
63- config .WorkspaceRoot = defaultWorkspaceRoot
64- }
63+ if config .TargetDir != "" {
64+ // Validate that the target directory exists.
65+ info , err := os .Stat (config .TargetDir )
66+ if err != nil {
67+ return nil , fmt .Errorf ("target directory %s does not exist: %w" , config .TargetDir , err )
68+ }
69+ if ! info .IsDir () {
70+ return nil , fmt .Errorf ("target directory %s is not a directory" , config .TargetDir )
71+ }
72+ log .Infof (ctx , "Using shared target directory: %s (per-task workspace isolation disabled)" , config .TargetDir )
73+ } else {
74+ if config .WorkspaceRoot == "" {
75+ config .WorkspaceRoot = defaultWorkspaceRoot
76+ }
6577
66- // Ensure workspace root exists.
67- if err := os .MkdirAll (config .WorkspaceRoot , 0755 ); err != nil {
68- return nil , fmt .Errorf ("failed to create workspace root %s: %w" , config .WorkspaceRoot , err )
78+ // Ensure workspace root exists.
79+ if err := os .MkdirAll (config .WorkspaceRoot , 0755 ); err != nil {
80+ return nil , fmt .Errorf ("failed to create workspace root %s: %w" , config .WorkspaceRoot , err )
81+ }
6982 }
7083
7184 return & DirectBackend {
@@ -78,14 +91,27 @@ func NewDirectBackend(ctx context.Context, config DirectBackendConfig) (*DirectB
7891func (b * DirectBackend ) ExecuteTask (ctx context.Context , params * TaskParams ) error {
7992 taskID := params .TaskID
8093
81- // 1. Create per-task workspace directory.
82- workspaceDir := filepath .Join (b .config .WorkspaceRoot , taskID )
83- if err := os .MkdirAll (workspaceDir , 0755 ); err != nil {
84- return fmt .Errorf ("failed to create workspace directory: %w" , err )
94+ // Determine working directory: shared target dir or per-task workspace.
95+ var workspaceDir string
96+ usingTargetDir := b .config .TargetDir != ""
97+
98+ if usingTargetDir {
99+ workspaceDir = b .config .TargetDir
100+ } else {
101+ // Create per-task workspace directory.
102+ workspaceDir = filepath .Join (b .config .WorkspaceRoot , taskID )
103+ if err := os .MkdirAll (workspaceDir , 0755 ); err != nil {
104+ return fmt .Errorf ("failed to create workspace directory: %w" , err )
105+ }
106+ log .Infof (ctx , "Created workspace: %s" , workspaceDir )
85107 }
86- log .Infof (ctx , "Created workspace: %s" , workspaceDir )
87108
88109 defer func () {
110+ if usingTargetDir {
111+ // Don't clean up the shared target directory.
112+ b .runTeardownIfConfigured (ctx , taskID , workspaceDir )
113+ return
114+ }
89115 if b .config .NoCleanup {
90116 log .Infof (ctx , "Skipping cleanup for workspace: %s" , workspaceDir )
91117 return
@@ -102,6 +128,7 @@ func (b *DirectBackend) ExecuteTask(ctx context.Context, params *TaskParams) err
102128 if err := envFile .Close (); err != nil {
103129 return fmt .Errorf ("failed to close environment file: %w" , err )
104130 }
131+ defer os .Remove (envFilePath )
105132
106133 // 3. Build environment variables: common + config-level.
107134 envVars := make ([]string , len (params .EnvVars ))
@@ -159,6 +186,9 @@ func (b *DirectBackend) ExecuteTask(ctx context.Context, params *TaskParams) err
159186
160187// Shutdown cleans up any workspace directories left behind under the workspace root.
161188func (b * DirectBackend ) Shutdown (ctx context.Context ) {
189+ if b .config .WorkspaceRoot == "" {
190+ return
191+ }
162192 entries , err := os .ReadDir (b .config .WorkspaceRoot )
163193 if err != nil {
164194 if ! os .IsNotExist (err ) {
@@ -176,20 +206,25 @@ func (b *DirectBackend) Shutdown(ctx context.Context) {
176206 }
177207}
178208
209+ // runTeardownIfConfigured runs the teardown command if one is configured.
210+ func (b * DirectBackend ) runTeardownIfConfigured (ctx context.Context , taskID , workspaceDir string ) {
211+ if b .config .TeardownCommand == "" {
212+ return
213+ }
214+ teardownEnv := []string {
215+ fmt .Sprintf ("OZ_WORKSPACE_ROOT=%s" , workspaceDir ),
216+ "OZ_WORKER_BACKEND=direct" ,
217+ fmt .Sprintf ("OZ_RUN_ID=%s" , taskID ),
218+ }
219+ log .Infof (ctx , "Running teardown command: %s" , b .config .TeardownCommand )
220+ if err := b .runCommand (ctx , b .config .TeardownCommand , workspaceDir , teardownEnv ); err != nil {
221+ log .Warnf (ctx , "Teardown command failed: %v" , err )
222+ }
223+ }
224+
179225// cleanup runs the teardown command (if configured) and removes the workspace directory.
180226func (b * DirectBackend ) cleanup (ctx context.Context , taskID , workspaceDir string ) {
181- if b .config .TeardownCommand != "" {
182- teardownEnv := []string {
183- fmt .Sprintf ("OZ_WORKSPACE_ROOT=%s" , workspaceDir ),
184- "OZ_WORKER_BACKEND=direct" ,
185- fmt .Sprintf ("OZ_RUN_ID=%s" , taskID ),
186- }
187-
188- log .Infof (ctx , "Running teardown command: %s" , b .config .TeardownCommand )
189- if err := b .runCommand (ctx , b .config .TeardownCommand , workspaceDir , teardownEnv ); err != nil {
190- log .Warnf (ctx , "Teardown command failed: %v" , err )
191- }
192- }
227+ b .runTeardownIfConfigured (ctx , taskID , workspaceDir )
193228
194229 log .Infof (ctx , "Removing workspace: %s" , workspaceDir )
195230 if err := os .RemoveAll (workspaceDir ); err != nil {
0 commit comments