Phase 2: Implement Execution Environment Abstraction (v0.3.0)
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>
This commit is contained in:
		
							
								
								
									
										453
									
								
								vendor/go.opentelemetry.io/otel/trace/internal/telemetry/value.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										453
									
								
								vendor/go.opentelemetry.io/otel/trace/internal/telemetry/value.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,453 @@ | ||||
| // Copyright The OpenTelemetry Authors | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
|  | ||||
| package telemetry // import "go.opentelemetry.io/otel/trace/internal/telemetry" | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"cmp" | ||||
| 	"encoding/base64" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"math" | ||||
| 	"slices" | ||||
| 	"strconv" | ||||
| 	"unsafe" | ||||
| ) | ||||
|  | ||||
| // A Value represents a structured value. | ||||
| // A zero value is valid and represents an empty value. | ||||
| type Value struct { | ||||
| 	// Ensure forward compatibility by explicitly making this not comparable. | ||||
| 	noCmp [0]func() //nolint: unused  // This is indeed used. | ||||
|  | ||||
| 	// num holds the value for Int64, Float64, and Bool. It holds the length | ||||
| 	// for String, Bytes, Slice, Map. | ||||
| 	num uint64 | ||||
| 	// any holds either the KindBool, KindInt64, KindFloat64, stringptr, | ||||
| 	// bytesptr, sliceptr, or mapptr. If KindBool, KindInt64, or KindFloat64 | ||||
| 	// then the value of Value is in num as described above. Otherwise, it | ||||
| 	// contains the value wrapped in the appropriate type. | ||||
| 	any any | ||||
| } | ||||
|  | ||||
| type ( | ||||
| 	// sliceptr represents a value in Value.any for KindString Values. | ||||
| 	stringptr *byte | ||||
| 	// bytesptr represents a value in Value.any for KindBytes Values. | ||||
| 	bytesptr *byte | ||||
| 	// sliceptr represents a value in Value.any for KindSlice Values. | ||||
| 	sliceptr *Value | ||||
| 	// mapptr represents a value in Value.any for KindMap Values. | ||||
| 	mapptr *Attr | ||||
| ) | ||||
|  | ||||
| // ValueKind is the kind of a [Value]. | ||||
| type ValueKind int | ||||
|  | ||||
| // ValueKind values. | ||||
| const ( | ||||
| 	ValueKindEmpty ValueKind = iota | ||||
| 	ValueKindBool | ||||
| 	ValueKindFloat64 | ||||
| 	ValueKindInt64 | ||||
| 	ValueKindString | ||||
| 	ValueKindBytes | ||||
| 	ValueKindSlice | ||||
| 	ValueKindMap | ||||
| ) | ||||
|  | ||||
| var valueKindStrings = []string{ | ||||
| 	"Empty", | ||||
| 	"Bool", | ||||
| 	"Float64", | ||||
| 	"Int64", | ||||
| 	"String", | ||||
| 	"Bytes", | ||||
| 	"Slice", | ||||
| 	"Map", | ||||
| } | ||||
|  | ||||
| func (k ValueKind) String() string { | ||||
| 	if k >= 0 && int(k) < len(valueKindStrings) { | ||||
| 		return valueKindStrings[k] | ||||
| 	} | ||||
| 	return "<unknown telemetry.ValueKind>" | ||||
| } | ||||
|  | ||||
| // StringValue returns a new [Value] for a string. | ||||
| func StringValue(v string) Value { | ||||
| 	return Value{ | ||||
| 		num: uint64(len(v)), | ||||
| 		any: stringptr(unsafe.StringData(v)), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // IntValue returns a [Value] for an int. | ||||
| func IntValue(v int) Value { return Int64Value(int64(v)) } | ||||
|  | ||||
| // Int64Value returns a [Value] for an int64. | ||||
| func Int64Value(v int64) Value { | ||||
| 	return Value{ | ||||
| 		num: uint64(v), // nolint: gosec  // Store raw bytes. | ||||
| 		any: ValueKindInt64, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Float64Value returns a [Value] for a float64. | ||||
| func Float64Value(v float64) Value { | ||||
| 	return Value{num: math.Float64bits(v), any: ValueKindFloat64} | ||||
| } | ||||
|  | ||||
| // BoolValue returns a [Value] for a bool. | ||||
| func BoolValue(v bool) Value { //nolint:revive // Not a control flag. | ||||
| 	var n uint64 | ||||
| 	if v { | ||||
| 		n = 1 | ||||
| 	} | ||||
| 	return Value{num: n, any: ValueKindBool} | ||||
| } | ||||
|  | ||||
| // BytesValue returns a [Value] for a byte slice. The passed slice must not be | ||||
| // changed after it is passed. | ||||
| func BytesValue(v []byte) Value { | ||||
| 	return Value{ | ||||
| 		num: uint64(len(v)), | ||||
| 		any: bytesptr(unsafe.SliceData(v)), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // SliceValue returns a [Value] for a slice of [Value]. The passed slice must | ||||
| // not be changed after it is passed. | ||||
| func SliceValue(vs ...Value) Value { | ||||
| 	return Value{ | ||||
| 		num: uint64(len(vs)), | ||||
| 		any: sliceptr(unsafe.SliceData(vs)), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // MapValue returns a new [Value] for a slice of key-value pairs. The passed | ||||
| // slice must not be changed after it is passed. | ||||
| func MapValue(kvs ...Attr) Value { | ||||
| 	return Value{ | ||||
| 		num: uint64(len(kvs)), | ||||
| 		any: mapptr(unsafe.SliceData(kvs)), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // AsString returns the value held by v as a string. | ||||
| func (v Value) AsString() string { | ||||
| 	if sp, ok := v.any.(stringptr); ok { | ||||
| 		return unsafe.String(sp, v.num) | ||||
| 	} | ||||
| 	// TODO: error handle | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| // asString returns the value held by v as a string. It will panic if the Value | ||||
| // is not KindString. | ||||
| func (v Value) asString() string { | ||||
| 	return unsafe.String(v.any.(stringptr), v.num) | ||||
| } | ||||
|  | ||||
| // AsInt64 returns the value held by v as an int64. | ||||
| func (v Value) AsInt64() int64 { | ||||
| 	if v.Kind() != ValueKindInt64 { | ||||
| 		// TODO: error handle | ||||
| 		return 0 | ||||
| 	} | ||||
| 	return v.asInt64() | ||||
| } | ||||
|  | ||||
| // asInt64 returns the value held by v as an int64. If v is not of KindInt64, | ||||
| // this will return garbage. | ||||
| func (v Value) asInt64() int64 { | ||||
| 	// Assumes v.num was a valid int64 (overflow not checked). | ||||
| 	return int64(v.num) // nolint: gosec | ||||
| } | ||||
|  | ||||
| // AsBool returns the value held by v as a bool. | ||||
| func (v Value) AsBool() bool { | ||||
| 	if v.Kind() != ValueKindBool { | ||||
| 		// TODO: error handle | ||||
| 		return false | ||||
| 	} | ||||
| 	return v.asBool() | ||||
| } | ||||
|  | ||||
| // asBool returns the value held by v as a bool. If v is not of KindBool, this | ||||
| // will return garbage. | ||||
| func (v Value) asBool() bool { return v.num == 1 } | ||||
|  | ||||
| // AsFloat64 returns the value held by v as a float64. | ||||
| func (v Value) AsFloat64() float64 { | ||||
| 	if v.Kind() != ValueKindFloat64 { | ||||
| 		// TODO: error handle | ||||
| 		return 0 | ||||
| 	} | ||||
| 	return v.asFloat64() | ||||
| } | ||||
|  | ||||
| // asFloat64 returns the value held by v as a float64. If v is not of | ||||
| // KindFloat64, this will return garbage. | ||||
| func (v Value) asFloat64() float64 { return math.Float64frombits(v.num) } | ||||
|  | ||||
| // AsBytes returns the value held by v as a []byte. | ||||
| func (v Value) AsBytes() []byte { | ||||
| 	if sp, ok := v.any.(bytesptr); ok { | ||||
| 		return unsafe.Slice((*byte)(sp), v.num) | ||||
| 	} | ||||
| 	// TODO: error handle | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // asBytes returns the value held by v as a []byte. It will panic if the Value | ||||
| // is not KindBytes. | ||||
| func (v Value) asBytes() []byte { | ||||
| 	return unsafe.Slice((*byte)(v.any.(bytesptr)), v.num) | ||||
| } | ||||
|  | ||||
| // AsSlice returns the value held by v as a []Value. | ||||
| func (v Value) AsSlice() []Value { | ||||
| 	if sp, ok := v.any.(sliceptr); ok { | ||||
| 		return unsafe.Slice((*Value)(sp), v.num) | ||||
| 	} | ||||
| 	// TODO: error handle | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // asSlice returns the value held by v as a []Value. It will panic if the Value | ||||
| // is not KindSlice. | ||||
| func (v Value) asSlice() []Value { | ||||
| 	return unsafe.Slice((*Value)(v.any.(sliceptr)), v.num) | ||||
| } | ||||
|  | ||||
| // AsMap returns the value held by v as a []Attr. | ||||
| func (v Value) AsMap() []Attr { | ||||
| 	if sp, ok := v.any.(mapptr); ok { | ||||
| 		return unsafe.Slice((*Attr)(sp), v.num) | ||||
| 	} | ||||
| 	// TODO: error handle | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // asMap returns the value held by v as a []Attr. It will panic if the | ||||
| // Value is not KindMap. | ||||
| func (v Value) asMap() []Attr { | ||||
| 	return unsafe.Slice((*Attr)(v.any.(mapptr)), v.num) | ||||
| } | ||||
|  | ||||
| // Kind returns the Kind of v. | ||||
| func (v Value) Kind() ValueKind { | ||||
| 	switch x := v.any.(type) { | ||||
| 	case ValueKind: | ||||
| 		return x | ||||
| 	case stringptr: | ||||
| 		return ValueKindString | ||||
| 	case bytesptr: | ||||
| 		return ValueKindBytes | ||||
| 	case sliceptr: | ||||
| 		return ValueKindSlice | ||||
| 	case mapptr: | ||||
| 		return ValueKindMap | ||||
| 	default: | ||||
| 		return ValueKindEmpty | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Empty reports whether v does not hold any value. | ||||
| func (v Value) Empty() bool { return v.Kind() == ValueKindEmpty } | ||||
|  | ||||
| // Equal reports whether v is equal to w. | ||||
| func (v Value) Equal(w Value) bool { | ||||
| 	k1 := v.Kind() | ||||
| 	k2 := w.Kind() | ||||
| 	if k1 != k2 { | ||||
| 		return false | ||||
| 	} | ||||
| 	switch k1 { | ||||
| 	case ValueKindInt64, ValueKindBool: | ||||
| 		return v.num == w.num | ||||
| 	case ValueKindString: | ||||
| 		return v.asString() == w.asString() | ||||
| 	case ValueKindFloat64: | ||||
| 		return v.asFloat64() == w.asFloat64() | ||||
| 	case ValueKindSlice: | ||||
| 		return slices.EqualFunc(v.asSlice(), w.asSlice(), Value.Equal) | ||||
| 	case ValueKindMap: | ||||
| 		sv := sortMap(v.asMap()) | ||||
| 		sw := sortMap(w.asMap()) | ||||
| 		return slices.EqualFunc(sv, sw, Attr.Equal) | ||||
| 	case ValueKindBytes: | ||||
| 		return bytes.Equal(v.asBytes(), w.asBytes()) | ||||
| 	case ValueKindEmpty: | ||||
| 		return true | ||||
| 	default: | ||||
| 		// TODO: error handle | ||||
| 		return false | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func sortMap(m []Attr) []Attr { | ||||
| 	sm := make([]Attr, len(m)) | ||||
| 	copy(sm, m) | ||||
| 	slices.SortFunc(sm, func(a, b Attr) int { | ||||
| 		return cmp.Compare(a.Key, b.Key) | ||||
| 	}) | ||||
|  | ||||
| 	return sm | ||||
| } | ||||
|  | ||||
| // String returns Value's value as a string, formatted like [fmt.Sprint]. | ||||
| // | ||||
| // The returned string is meant for debugging; | ||||
| // the string representation is not stable. | ||||
| func (v Value) String() string { | ||||
| 	switch v.Kind() { | ||||
| 	case ValueKindString: | ||||
| 		return v.asString() | ||||
| 	case ValueKindInt64: | ||||
| 		// Assumes v.num was a valid int64 (overflow not checked). | ||||
| 		return strconv.FormatInt(int64(v.num), 10) // nolint: gosec | ||||
| 	case ValueKindFloat64: | ||||
| 		return strconv.FormatFloat(v.asFloat64(), 'g', -1, 64) | ||||
| 	case ValueKindBool: | ||||
| 		return strconv.FormatBool(v.asBool()) | ||||
| 	case ValueKindBytes: | ||||
| 		return string(v.asBytes()) | ||||
| 	case ValueKindMap: | ||||
| 		return fmt.Sprint(v.asMap()) | ||||
| 	case ValueKindSlice: | ||||
| 		return fmt.Sprint(v.asSlice()) | ||||
| 	case ValueKindEmpty: | ||||
| 		return "<nil>" | ||||
| 	default: | ||||
| 		// Try to handle this as gracefully as possible. | ||||
| 		// | ||||
| 		// Don't panic here. The goal here is to have developers find this | ||||
| 		// first if a slog.Kind is is not handled. It is | ||||
| 		// preferable to have user's open issue asking why their attributes | ||||
| 		// have a "unhandled: " prefix than say that their code is panicking. | ||||
| 		return fmt.Sprintf("<unhandled telemetry.ValueKind: %s>", v.Kind()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // MarshalJSON encodes v into OTLP formatted JSON. | ||||
| func (v *Value) MarshalJSON() ([]byte, error) { | ||||
| 	switch v.Kind() { | ||||
| 	case ValueKindString: | ||||
| 		return json.Marshal(struct { | ||||
| 			Value string `json:"stringValue"` | ||||
| 		}{v.asString()}) | ||||
| 	case ValueKindInt64: | ||||
| 		return json.Marshal(struct { | ||||
| 			Value string `json:"intValue"` | ||||
| 		}{strconv.FormatInt(int64(v.num), 10)}) // nolint: gosec  // From raw bytes. | ||||
| 	case ValueKindFloat64: | ||||
| 		return json.Marshal(struct { | ||||
| 			Value float64 `json:"doubleValue"` | ||||
| 		}{v.asFloat64()}) | ||||
| 	case ValueKindBool: | ||||
| 		return json.Marshal(struct { | ||||
| 			Value bool `json:"boolValue"` | ||||
| 		}{v.asBool()}) | ||||
| 	case ValueKindBytes: | ||||
| 		return json.Marshal(struct { | ||||
| 			Value []byte `json:"bytesValue"` | ||||
| 		}{v.asBytes()}) | ||||
| 	case ValueKindMap: | ||||
| 		return json.Marshal(struct { | ||||
| 			Value struct { | ||||
| 				Values []Attr `json:"values"` | ||||
| 			} `json:"kvlistValue"` | ||||
| 		}{struct { | ||||
| 			Values []Attr `json:"values"` | ||||
| 		}{v.asMap()}}) | ||||
| 	case ValueKindSlice: | ||||
| 		return json.Marshal(struct { | ||||
| 			Value struct { | ||||
| 				Values []Value `json:"values"` | ||||
| 			} `json:"arrayValue"` | ||||
| 		}{struct { | ||||
| 			Values []Value `json:"values"` | ||||
| 		}{v.asSlice()}}) | ||||
| 	case ValueKindEmpty: | ||||
| 		return nil, nil | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("unknown Value kind: %s", v.Kind().String()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // UnmarshalJSON decodes the OTLP formatted JSON contained in data into v. | ||||
| func (v *Value) UnmarshalJSON(data []byte) error { | ||||
| 	decoder := json.NewDecoder(bytes.NewReader(data)) | ||||
|  | ||||
| 	t, err := decoder.Token() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if t != json.Delim('{') { | ||||
| 		return errors.New("invalid Value type") | ||||
| 	} | ||||
|  | ||||
| 	for decoder.More() { | ||||
| 		keyIface, err := decoder.Token() | ||||
| 		if err != nil { | ||||
| 			if errors.Is(err, io.EOF) { | ||||
| 				// Empty. | ||||
| 				return nil | ||||
| 			} | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		key, ok := keyIface.(string) | ||||
| 		if !ok { | ||||
| 			return fmt.Errorf("invalid Value key: %#v", keyIface) | ||||
| 		} | ||||
|  | ||||
| 		switch key { | ||||
| 		case "stringValue", "string_value": | ||||
| 			var val string | ||||
| 			err = decoder.Decode(&val) | ||||
| 			*v = StringValue(val) | ||||
| 		case "boolValue", "bool_value": | ||||
| 			var val bool | ||||
| 			err = decoder.Decode(&val) | ||||
| 			*v = BoolValue(val) | ||||
| 		case "intValue", "int_value": | ||||
| 			var val protoInt64 | ||||
| 			err = decoder.Decode(&val) | ||||
| 			*v = Int64Value(val.Int64()) | ||||
| 		case "doubleValue", "double_value": | ||||
| 			var val float64 | ||||
| 			err = decoder.Decode(&val) | ||||
| 			*v = Float64Value(val) | ||||
| 		case "bytesValue", "bytes_value": | ||||
| 			var val64 string | ||||
| 			if err := decoder.Decode(&val64); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			var val []byte | ||||
| 			val, err = base64.StdEncoding.DecodeString(val64) | ||||
| 			*v = BytesValue(val) | ||||
| 		case "arrayValue", "array_value": | ||||
| 			var val struct{ Values []Value } | ||||
| 			err = decoder.Decode(&val) | ||||
| 			*v = SliceValue(val.Values...) | ||||
| 		case "kvlistValue", "kvlist_value": | ||||
| 			var val struct{ Values []Attr } | ||||
| 			err = decoder.Decode(&val) | ||||
| 			*v = MapValue(val.Values...) | ||||
| 		default: | ||||
| 			// Skip unknown. | ||||
| 			continue | ||||
| 		} | ||||
| 		// Use first valid. Ignore the rest. | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Only unknown fields. Return nil without unmarshaling any value. | ||||
| 	return nil | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 anthonyrawlins
					anthonyrawlins