 8d9b62daf3
			
		
	
	8d9b62daf3
	
	
	
		
			
			This commit implements Phase 2 of the CHORUS Task Execution Engine development plan, providing a comprehensive execution environment abstraction layer with Docker container sandboxing support. ## New Features ### Core Sandbox Interface - Comprehensive ExecutionSandbox interface with isolated task execution - Support for command execution, file I/O, environment management - Resource usage monitoring and sandbox lifecycle management - Standardized error handling with SandboxError types and categories ### Docker Container Sandbox Implementation - Full Docker API integration with secure container creation - Transparent repository mounting with configurable read/write access - Advanced security policies with capability dropping and privilege controls - Comprehensive resource limits (CPU, memory, disk, processes, file handles) - Support for tmpfs mounts, masked paths, and read-only bind mounts - Container lifecycle management with proper cleanup and health monitoring ### Security & Resource Management - Configurable security policies with SELinux, AppArmor, and Seccomp support - Fine-grained capability management with secure defaults - Network isolation options with configurable DNS and proxy settings - Resource monitoring with real-time CPU, memory, and network usage tracking - Comprehensive ulimits configuration for process and file handle limits ### Repository Integration - Seamless repository mounting from local paths to container workspaces - Git configuration support with user credentials and global settings - File inclusion/exclusion patterns for selective repository access - Configurable permissions and ownership for mounted repositories ### Testing Infrastructure - Comprehensive test suite with 60+ test cases covering all functionality - Docker integration tests with Alpine Linux containers (skipped in short mode) - Mock sandbox implementation for unit testing without Docker dependencies - Security policy validation tests with read-only filesystem enforcement - Resource usage monitoring and cleanup verification tests ## Technical Details ### Dependencies Added - github.com/docker/docker v28.4.0+incompatible - Docker API client - github.com/docker/go-connections v0.6.0 - Docker connection utilities - github.com/docker/go-units v0.5.0 - Docker units and formatting - Associated Docker API dependencies for complete container management ### Architecture - Interface-driven design enabling multiple sandbox implementations - Comprehensive configuration structures for all sandbox aspects - Resource usage tracking with detailed metrics collection - Error handling with retryable error classification - Proper cleanup and resource management throughout sandbox lifecycle ### Compatibility - Maintains backward compatibility with existing CHORUS architecture - Designed for future integration with Phase 3 Core Task Execution Engine - Extensible design supporting additional sandbox implementations (VM, process) This Phase 2 implementation provides the foundation for secure, isolated task execution that will be integrated with the AI model providers from Phase 1 in the upcoming Phase 3 development. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			238 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			238 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package suite
 | |
| 
 | |
| import (
 | |
| 	"flag"
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"reflect"
 | |
| 	"regexp"
 | |
| 	"runtime/debug"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/stretchr/testify/assert"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| )
 | |
| 
 | |
| var matchMethod = flag.String("testify.m", "", "regular expression to select tests of the testify suite to run")
 | |
| 
 | |
| // Suite is a basic testing suite with methods for storing and
 | |
| // retrieving the current *testing.T context.
 | |
| type Suite struct {
 | |
| 	*assert.Assertions
 | |
| 
 | |
| 	mu      sync.RWMutex
 | |
| 	require *require.Assertions
 | |
| 	t       *testing.T
 | |
| 
 | |
| 	// Parent suite to have access to the implemented methods of parent struct
 | |
| 	s TestingSuite
 | |
| }
 | |
| 
 | |
| // T retrieves the current *testing.T context.
 | |
| func (suite *Suite) T() *testing.T {
 | |
| 	suite.mu.RLock()
 | |
| 	defer suite.mu.RUnlock()
 | |
| 	return suite.t
 | |
| }
 | |
| 
 | |
| // SetT sets the current *testing.T context.
 | |
| func (suite *Suite) SetT(t *testing.T) {
 | |
| 	suite.mu.Lock()
 | |
| 	defer suite.mu.Unlock()
 | |
| 	suite.t = t
 | |
| 	suite.Assertions = assert.New(t)
 | |
| 	suite.require = require.New(t)
 | |
| }
 | |
| 
 | |
| // SetS needs to set the current test suite as parent
 | |
| // to get access to the parent methods
 | |
| func (suite *Suite) SetS(s TestingSuite) {
 | |
| 	suite.s = s
 | |
| }
 | |
| 
 | |
| // Require returns a require context for suite.
 | |
| func (suite *Suite) Require() *require.Assertions {
 | |
| 	suite.mu.Lock()
 | |
| 	defer suite.mu.Unlock()
 | |
| 	if suite.require == nil {
 | |
| 		panic("'Require' must not be called before 'Run' or 'SetT'")
 | |
| 	}
 | |
| 	return suite.require
 | |
| }
 | |
| 
 | |
| // Assert returns an assert context for suite.  Normally, you can call
 | |
| // `suite.NoError(expected, actual)`, but for situations where the embedded
 | |
| // methods are overridden (for example, you might want to override
 | |
| // assert.Assertions with require.Assertions), this method is provided so you
 | |
| // can call `suite.Assert().NoError()`.
 | |
| func (suite *Suite) Assert() *assert.Assertions {
 | |
| 	suite.mu.Lock()
 | |
| 	defer suite.mu.Unlock()
 | |
| 	if suite.Assertions == nil {
 | |
| 		panic("'Assert' must not be called before 'Run' or 'SetT'")
 | |
| 	}
 | |
| 	return suite.Assertions
 | |
| }
 | |
| 
 | |
| func recoverAndFailOnPanic(t *testing.T) {
 | |
| 	t.Helper()
 | |
| 	r := recover()
 | |
| 	failOnPanic(t, r)
 | |
| }
 | |
| 
 | |
| func failOnPanic(t *testing.T, r interface{}) {
 | |
| 	t.Helper()
 | |
| 	if r != nil {
 | |
| 		t.Errorf("test panicked: %v\n%s", r, debug.Stack())
 | |
| 		t.FailNow()
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Run provides suite functionality around golang subtests.  It should be
 | |
| // called in place of t.Run(name, func(t *testing.T)) in test suite code.
 | |
| // The passed-in func will be executed as a subtest with a fresh instance of t.
 | |
| // Provides compatibility with go test pkg -run TestSuite/TestName/SubTestName.
 | |
| func (suite *Suite) Run(name string, subtest func()) bool {
 | |
| 	oldT := suite.T()
 | |
| 
 | |
| 	return oldT.Run(name, func(t *testing.T) {
 | |
| 		suite.SetT(t)
 | |
| 		defer suite.SetT(oldT)
 | |
| 
 | |
| 		defer recoverAndFailOnPanic(t)
 | |
| 
 | |
| 		if setupSubTest, ok := suite.s.(SetupSubTest); ok {
 | |
| 			setupSubTest.SetupSubTest()
 | |
| 		}
 | |
| 
 | |
| 		if tearDownSubTest, ok := suite.s.(TearDownSubTest); ok {
 | |
| 			defer tearDownSubTest.TearDownSubTest()
 | |
| 		}
 | |
| 
 | |
| 		subtest()
 | |
| 	})
 | |
| }
 | |
| 
 | |
| type test = struct {
 | |
| 	name string
 | |
| 	run  func(t *testing.T)
 | |
| }
 | |
| 
 | |
| // Run takes a testing suite and runs all of the tests attached
 | |
| // to it.
 | |
| func Run(t *testing.T, suite TestingSuite) {
 | |
| 	defer recoverAndFailOnPanic(t)
 | |
| 
 | |
| 	suite.SetT(t)
 | |
| 	suite.SetS(suite)
 | |
| 
 | |
| 	var stats *SuiteInformation
 | |
| 	if _, ok := suite.(WithStats); ok {
 | |
| 		stats = newSuiteInformation()
 | |
| 	}
 | |
| 
 | |
| 	var tests []test
 | |
| 	methodFinder := reflect.TypeOf(suite)
 | |
| 	suiteName := methodFinder.Elem().Name()
 | |
| 
 | |
| 	var matchMethodRE *regexp.Regexp
 | |
| 	if *matchMethod != "" {
 | |
| 		var err error
 | |
| 		matchMethodRE, err = regexp.Compile(*matchMethod)
 | |
| 		if err != nil {
 | |
| 			fmt.Fprintf(os.Stderr, "testify: invalid regexp for -m: %s\n", err)
 | |
| 			os.Exit(1)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for i := 0; i < methodFinder.NumMethod(); i++ {
 | |
| 		method := methodFinder.Method(i)
 | |
| 
 | |
| 		if !strings.HasPrefix(method.Name, "Test") {
 | |
| 			continue
 | |
| 		}
 | |
| 		// Apply -testify.m filter
 | |
| 		if matchMethodRE != nil && !matchMethodRE.MatchString(method.Name) {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		test := test{
 | |
| 			name: method.Name,
 | |
| 			run: func(t *testing.T) {
 | |
| 				parentT := suite.T()
 | |
| 				suite.SetT(t)
 | |
| 				defer recoverAndFailOnPanic(t)
 | |
| 				defer func() {
 | |
| 					t.Helper()
 | |
| 
 | |
| 					r := recover()
 | |
| 
 | |
| 					stats.end(method.Name, !t.Failed() && r == nil)
 | |
| 
 | |
| 					if afterTestSuite, ok := suite.(AfterTest); ok {
 | |
| 						afterTestSuite.AfterTest(suiteName, method.Name)
 | |
| 					}
 | |
| 
 | |
| 					if tearDownTestSuite, ok := suite.(TearDownTestSuite); ok {
 | |
| 						tearDownTestSuite.TearDownTest()
 | |
| 					}
 | |
| 
 | |
| 					suite.SetT(parentT)
 | |
| 					failOnPanic(t, r)
 | |
| 				}()
 | |
| 
 | |
| 				if setupTestSuite, ok := suite.(SetupTestSuite); ok {
 | |
| 					setupTestSuite.SetupTest()
 | |
| 				}
 | |
| 				if beforeTestSuite, ok := suite.(BeforeTest); ok {
 | |
| 					beforeTestSuite.BeforeTest(methodFinder.Elem().Name(), method.Name)
 | |
| 				}
 | |
| 
 | |
| 				stats.start(method.Name)
 | |
| 
 | |
| 				method.Func.Call([]reflect.Value{reflect.ValueOf(suite)})
 | |
| 			},
 | |
| 		}
 | |
| 		tests = append(tests, test)
 | |
| 	}
 | |
| 
 | |
| 	if len(tests) == 0 {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if stats != nil {
 | |
| 		stats.Start = time.Now()
 | |
| 	}
 | |
| 
 | |
| 	if setupAllSuite, ok := suite.(SetupAllSuite); ok {
 | |
| 		setupAllSuite.SetupSuite()
 | |
| 	}
 | |
| 
 | |
| 	defer func() {
 | |
| 		if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok {
 | |
| 			tearDownAllSuite.TearDownSuite()
 | |
| 		}
 | |
| 
 | |
| 		if suiteWithStats, measureStats := suite.(WithStats); measureStats {
 | |
| 			stats.End = time.Now()
 | |
| 			suiteWithStats.HandleStats(suiteName, stats)
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	runTests(t, tests)
 | |
| }
 | |
| 
 | |
| func runTests(t *testing.T, tests []test) {
 | |
| 	if len(tests) == 0 {
 | |
| 		t.Log("warning: no tests to run")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	for _, test := range tests {
 | |
| 		t.Run(test.name, test.run)
 | |
| 	}
 | |
| }
 |