refactor(config): remove unused mixed-mode environment config

This commit is contained in:
2026-09-30 15:54:47 +08:00
parent 3ad606b3f5
commit 7d69a24b4e
6 changed files with 17 additions and 336 deletions
-11
View File
@@ -52,14 +52,3 @@ func TestParseCertificateFingerprints(t *testing.T) {
t.Fatal("expected invalid fingerprint to be rejected")
}
}
func TestConfigRequiresDispatcherMTLSForEndpointInventory(t *testing.T) {
cfg := Config{Mode: "mock", DBPath: ":memory:", AgentEndpointsFile: "agents.json"}
if err := cfg.Validate("dispatcher"); err == nil {
t.Fatal("expected Dispatcher mTLS requirement")
}
cfg.MTLSCAFile, cfg.MTLSCertFile, cfg.MTLSKeyFile = "ca.pem", "dispatcher.pem", "dispatcher.key"
if err := cfg.Validate("dispatcher"); err != nil {
t.Fatal(err)
}
}
-208
View File
@@ -1,208 +0,0 @@
package config
import (
"errors"
"fmt"
"os"
"strconv"
"strings"
"time"
)
type Config struct {
Mode string
DBPath string
SpoolRoot string
RabbitURL string
Exchange string
OutboxBatch int
AgentID string
DispatcherID string
Version string
CellID string
GRPCListen string
DispatcherGRPCListen string
DispatcherGRPCEndpoint string
DispatcherGRPCServerName string
DispatcherGRPCAllowedAgentIDs string
MTLSCAFile string
MTLSCertFile string
MTLSKeyFile string
MTLSServerName string
MTLSPeerCertificateFingerprints string
OSSRegion string
OSSEndpoint string
OSSBucket string
OSSAccessKeyID string
OSSAccessKeySecret string
OSSKeyPrefix string
OSSGrantTTL time.Duration
OSSMaxAssetBytes int64
StaticArtifactPath string
AgentEndpointsFile string
CallLogPath string
CallLogPhoneKey string
ARIURL string
ARIWebsocketURL string
ARIApplication string
ARIUsername string
ARIPassword string
CallTarget string
CallTrunkID string
CallCallerID string
CallMediaBind string
CallMediaPort int
CallRecordingDirectory string
CallAISnapshotPath string
CallTenantID string
CallTenantKey string
CallTaskID string
CallTaskItemID string
CallExecutionID string
}
func FromEnv() (Config, error) {
mediaPort, err := envInt("AGENT_CALL_MEDIA_PORT", 12000)
if err != nil {
return Config{}, err
}
return Config{
Mode: envOr("SIP_GO_AGENT_MODE", "mock"),
DBPath: envOr("DISPATCHER_DB", "dispatcher.db"),
SpoolRoot: envOr("AGENT_SPOOL", "./spool"),
RabbitURL: os.Getenv("RABBITMQ_URL"),
Exchange: envOr("RABBITMQ_EXCHANGE", "agent-call.commands.v1"),
OutboxBatch: 50,
AgentID: envOr("AGENT_ID", "agent-local"),
DispatcherID: envOr("DISPATCHER_ID", "dispatcher-local"),
Version: envOr("AGENT_VERSION", "dev"),
CellID: envOr("CELL_ID", "cell-local"),
GRPCListen: os.Getenv("AGENT_GRPC_LISTEN"),
DispatcherGRPCListen: os.Getenv("DISPATCHER_GRPC_LISTEN"),
DispatcherGRPCEndpoint: os.Getenv("DISPATCHER_GRPC_ENDPOINT"),
DispatcherGRPCServerName: envOr("DISPATCHER_GRPC_SERVER_NAME", os.Getenv("MTLS_SERVER_NAME")),
DispatcherGRPCAllowedAgentIDs: os.Getenv("DISPATCHER_ALLOWED_AGENT_IDS"),
MTLSCAFile: os.Getenv("MTLS_CA_FILE"),
MTLSCertFile: os.Getenv("MTLS_CERT_FILE"),
MTLSKeyFile: os.Getenv("MTLS_KEY_FILE"),
MTLSServerName: os.Getenv("MTLS_SERVER_NAME"),
MTLSPeerCertificateFingerprints: os.Getenv("MTLS_PEER_CERT_FINGERPRINTS"),
OSSMaxAssetBytes: 64 << 20,
StaticArtifactPath: os.Getenv("AGENT_STATIC_ARTIFACT"),
AgentEndpointsFile: os.Getenv("DISPATCHER_AGENT_ENDPOINTS_FILE"),
CallLogPath: os.Getenv("AGENT_CALL_BUSINESS_LOG"),
CallLogPhoneKey: os.Getenv("AGENT_CALL_PHONE_LOG_KEY"),
ARIURL: envOr("ARI_URL", "http://127.0.0.1:8088/ari"),
ARIWebsocketURL: envOr("ARI_WS_URL", "ws://127.0.0.1:8088/ari/events"),
ARIApplication: envOr("ARI_APPLICATION", "agent-call"),
ARIUsername: os.Getenv("ARI_USERNAME"),
ARIPassword: os.Getenv("ARI_PASSWORD"),
CallTarget: os.Getenv("AGENT_CALL_TARGET"),
CallTrunkID: os.Getenv("AGENT_CALL_TRUNK_ID"),
CallCallerID: os.Getenv("AGENT_CALL_CALLER_ID"),
CallMediaBind: envOr("AGENT_CALL_MEDIA_BIND", "127.0.0.1"),
CallMediaPort: mediaPort,
CallRecordingDirectory: envOr("AGENT_CALL_RECORDING_DIR", "./recordings"),
CallAISnapshotPath: os.Getenv("AGENT_CALL_AI_SNAPSHOT"),
CallTenantID: os.Getenv("AGENT_CALL_TENANT_ID"),
CallTenantKey: os.Getenv("AGENT_CALL_TENANT_KEY"),
CallTaskID: os.Getenv("AGENT_CALL_TASK_ID"),
CallTaskItemID: os.Getenv("AGENT_CALL_TASK_ITEM_ID"),
CallExecutionID: os.Getenv("AGENT_CALL_EXECUTION_ID"),
}, nil
}
func (c Config) Validate(role string) error {
if c.Mode != "mock" && c.Mode != "mixed" && c.Mode != "real" {
return fmt.Errorf("unsupported mode %q", c.Mode)
}
if role == "dispatcher" && strings.TrimSpace(c.DBPath) == "" {
return errors.New("dispatcher DB path is required")
}
if role == "agent" && strings.TrimSpace(c.SpoolRoot) == "" {
return errors.New("agent spool root is required")
}
if role == "agent" && strings.TrimSpace(c.CallLogPath) != "" && strings.TrimSpace(c.CallLogPhoneKey) == "" {
return errors.New("AGENT_CALL_PHONE_LOG_KEY is required when AGENT_CALL_BUSINESS_LOG is set")
}
if role == "agent" && strings.TrimSpace(c.CallLogPhoneKey) != "" && len([]byte(c.CallLogPhoneKey)) < 16 {
return errors.New("AGENT_CALL_PHONE_LOG_KEY must contain at least 16 bytes")
}
if role == "agent" && strings.TrimSpace(c.GRPCListen) != "" {
for name, value := range map[string]string{"MTLS_CA_FILE": c.MTLSCAFile, "MTLS_CERT_FILE": c.MTLSCertFile, "MTLS_KEY_FILE": c.MTLSKeyFile} {
if strings.TrimSpace(value) == "" {
return fmt.Errorf("%s is required when AGENT_GRPC_LISTEN is enabled", name)
}
}
}
if role == "agent" && c.Mode == "real" && strings.TrimSpace(c.GRPCListen) == "" {
return errors.New("real agent mode requires AGENT_GRPC_LISTEN")
}
if role == "agent" && c.Mode == "real" && strings.TrimSpace(c.StaticArtifactPath) == "" {
return errors.New("real agent mode requires AGENT_STATIC_ARTIFACT")
}
if role == "agent" && strings.TrimSpace(c.MTLSPeerCertificateFingerprints) != "" {
if _, err := ParseCertificateFingerprints(c.MTLSPeerCertificateFingerprints); err != nil {
return fmt.Errorf("invalid MTLS_PEER_CERT_FINGERPRINTS: %w", err)
}
}
if role == "agent" && strings.TrimSpace(c.DispatcherGRPCEndpoint) != "" {
for name, value := range map[string]string{"MTLS_CA_FILE": c.MTLSCAFile, "MTLS_CERT_FILE": c.MTLSCertFile, "MTLS_KEY_FILE": c.MTLSKeyFile} {
if strings.TrimSpace(value) == "" {
return fmt.Errorf("%s is required when DISPATCHER_GRPC_ENDPOINT is enabled", name)
}
}
}
if role == "dispatcher" && strings.TrimSpace(c.DispatcherGRPCListen) != "" {
for name, value := range map[string]string{"MTLS_CA_FILE": c.MTLSCAFile, "MTLS_CERT_FILE": c.MTLSCertFile, "MTLS_KEY_FILE": c.MTLSKeyFile} {
if strings.TrimSpace(value) == "" {
return fmt.Errorf("%s is required when DISPATCHER_GRPC_LISTEN is enabled", name)
}
}
for name, value := range map[string]string{"oss.region": c.OSSRegion, "oss.endpoint": c.OSSEndpoint, "oss.bucket": c.OSSBucket, "oss.access_key_id_env reference": c.OSSAccessKeyID, "oss.access_key_secret_env reference": c.OSSAccessKeySecret} {
if strings.TrimSpace(value) == "" {
return fmt.Errorf("%s must be supplied by the Dispatcher configuration file", name)
}
}
if strings.TrimSpace(c.DispatcherGRPCAllowedAgentIDs) == "" {
return errors.New("DISPATCHER_ALLOWED_AGENT_IDS is required when Dispatcher gRPC is enabled")
}
fingerprints, err := ParseCertificateFingerprints(c.MTLSPeerCertificateFingerprints)
if err != nil {
return fmt.Errorf("invalid MTLS_PEER_CERT_FINGERPRINTS: %w", err)
}
if len(fingerprints) == 0 {
return errors.New("MTLS_PEER_CERT_FINGERPRINTS is required when Dispatcher gRPC is enabled")
}
}
if role == "dispatcher" && strings.TrimSpace(c.AgentEndpointsFile) != "" {
for name, value := range map[string]string{"MTLS_CA_FILE": c.MTLSCAFile, "MTLS_CERT_FILE": c.MTLSCertFile, "MTLS_KEY_FILE": c.MTLSKeyFile} {
if strings.TrimSpace(value) == "" {
return fmt.Errorf("%s is required when DISPATCHER_AGENT_ENDPOINTS_FILE is enabled", name)
}
}
}
if c.Mode == "real" && strings.TrimSpace(c.RabbitURL) == "" && role == "dispatcher" {
return errors.New("real dispatcher mode requires RABBITMQ_URL")
}
return nil
}
func envOr(key, fallback string) string {
if value := strings.TrimSpace(os.Getenv(key)); value != "" {
return value
}
return fallback
}
func envInt(key string, fallback int) (int, error) {
value := strings.TrimSpace(os.Getenv(key))
if value == "" {
return fallback, nil
}
parsed, err := strconv.Atoi(value)
if err != nil {
return 0, fmt.Errorf("invalid %s: %w", key, err)
}
return parsed, nil
}
-100
View File
@@ -1,100 +0,0 @@
package config
import (
"strings"
"testing"
)
func TestConfigRejectsRealDispatcherWithoutBroker(t *testing.T) {
c, err := FromEnv()
if err != nil {
t.Fatal(err)
}
c.Mode, c.RabbitURL, c.DBPath = "real", "", ":memory:"
if err := c.Validate("dispatcher"); err == nil {
t.Fatal("expected real-mode broker requirement")
}
}
func TestFromEnvRejectsInvalidMediaPort(t *testing.T) {
t.Setenv("AGENT_CALL_MEDIA_PORT", "not-a-port")
if _, err := FromEnv(); err == nil || !strings.Contains(err.Error(), "AGENT_CALL_MEDIA_PORT") {
t.Fatalf("expected explicit media port error, got %v", err)
}
t.Setenv("AGENT_CALL_MEDIA_PORT", "43210")
cfg, err := FromEnv()
if err != nil || cfg.CallMediaPort != 43210 {
t.Fatalf("expected configured port, got %d, %v", cfg.CallMediaPort, err)
}
t.Setenv("AGENT_CALL_MEDIA_PORT", "")
cfg, err = FromEnv()
if err != nil || cfg.CallMediaPort != 12000 {
t.Fatalf("expected default port, got %d, %v", cfg.CallMediaPort, err)
}
}
func TestConfigAcceptsMockAgent(t *testing.T) {
c := Config{Mode: "mock", SpoolRoot: t.TempDir()}
if err := c.Validate("agent"); err != nil {
t.Fatal(err)
}
}
func TestConfigLoadsUnifiedDispatcherGRPCSettings(t *testing.T) {
t.Setenv("DISPATCHER_GRPC_LISTEN", "127.0.0.1:19443")
t.Setenv("DISPATCHER_GRPC_ENDPOINT", "dispatcher.test:19443")
t.Setenv("DISPATCHER_GRPC_SERVER_NAME", "dispatcher.test")
t.Setenv("DISPATCHER_ALLOWED_AGENT_IDS", "agent-cell-a,agent-cell-b")
c, err := FromEnv()
if err != nil {
t.Fatal(err)
}
if c.DispatcherGRPCListen != "127.0.0.1:19443" || c.DispatcherGRPCEndpoint != "dispatcher.test:19443" || c.DispatcherGRPCServerName != "dispatcher.test" || c.DispatcherGRPCAllowedAgentIDs != "agent-cell-a,agent-cell-b" {
t.Fatalf("unified Dispatcher gRPC settings were not loaded: %+v", c)
}
}
func TestConfigRequiresPhoneLogPair(t *testing.T) {
c := Config{Mode: "mock", SpoolRoot: t.TempDir(), CallLogPath: "calls.jsonl"}
if err := c.Validate("agent"); err == nil {
t.Fatal("expected phone log key requirement")
}
c.CallLogPhoneKey = "0123456789abcdef"
if err := c.Validate("agent"); err != nil {
t.Fatal(err)
}
c.CallLogPath = ""
if err := c.Validate("agent"); err != nil {
t.Fatalf("key-only configuration should use spool fallback: %v", err)
}
c.CallLogPhoneKey = "short"
if err := c.Validate("agent"); err == nil {
t.Fatal("expected phone log key length requirement")
}
}
func TestConfigRequiresAgentTLSFilesWhenRPCIsEnabled(t *testing.T) {
c := Config{Mode: "mock", SpoolRoot: t.TempDir(), GRPCListen: "127.0.0.1:19090"}
if err := c.Validate("agent"); err == nil {
t.Fatal("expected mTLS file requirement")
}
c.MTLSCAFile, c.MTLSCertFile, c.MTLSKeyFile = "ca.pem", "agent.pem", "agent.key"
if err := c.Validate("agent"); err != nil {
t.Fatal(err)
}
c.Mode, c.GRPCListen = "real", ""
if err := c.Validate("agent"); err == nil {
t.Fatal("expected real agent listener requirement")
}
}
func TestConfigRequiresStaticArtifactForRealAgent(t *testing.T) {
c := Config{Mode: "real", SpoolRoot: t.TempDir(), GRPCListen: "127.0.0.1:19090", MTLSCAFile: "ca.pem", MTLSCertFile: "agent.pem", MTLSKeyFile: "agent.key"}
if err := c.Validate("agent"); err == nil {
t.Fatal("expected static artifact requirement")
}
c.StaticArtifactPath = "artifact.json"
if err := c.Validate("agent"); err != nil {
t.Fatal(err)
}
}
+15
View File
@@ -33,6 +33,21 @@ func TestLoadCurrentOSSConfigRequiresBoundLocalHTTPSAndExplicitCredentials(t *te
}
}
func TestLoadCurrentOSSConfigIgnoresLegacyOSSEnvironment(t *testing.T) {
for _, key := range []string{"DISPATCHER_OSS_REGION", "DISPATCHER_OSS_ENDPOINT", "DISPATCHER_OSS_BUCKET", "DISPATCHER_OSS_ACCESS_KEY_ID", "DISPATCHER_OSS_ACCESS_KEY_SECRET", "DISPATCHER_OSS_KEY_PREFIX", "DISPATCHER_OSS_GRANT_TTL_SECONDS"} {
t.Setenv(key, "legacy-value")
}
t.Setenv("MOCK_OSS_KEY_ID", "isolated-id")
t.Setenv("MOCK_OSS_KEY_SECRET", "isolated-secret")
configuration, err := LoadCurrentOSSConfig(writeMockOSSDeployment(t, mockOSSDeployment), dispatcherFixtureID)
if err != nil {
t.Fatal(err)
}
if configuration.Endpoint != "https://127.0.0.1:19445" || configuration.Region != "cn-mock" || configuration.Bucket != "mock-bucket" || configuration.KeyPrefix != "approved" || configuration.GrantTTL != 15*time.Minute || configuration.AccessKeyID != "isolated-id" || configuration.AccessKeySecret != "isolated-secret" {
t.Fatal("legacy OSS environment overrode the approved file and credential references")
}
}
func TestLoadCurrentOSSConfigRejectsUnapprovedDeploymentWithoutLeakingCredentials(t *testing.T) {
t.Setenv("MOCK_OSS_KEY_ID", "isolated-id")
t.Setenv("MOCK_OSS_KEY_SECRET", "isolated-secret")
-17
View File
@@ -1,17 +0,0 @@
package config
import "testing"
func TestLegacyOSSEnvironmentIsNotAConfigurationSource(t *testing.T) {
for _, key := range []string{"DISPATCHER_OSS_REGION", "DISPATCHER_OSS_ENDPOINT", "DISPATCHER_OSS_BUCKET", "DISPATCHER_OSS_ACCESS_KEY_ID", "DISPATCHER_OSS_ACCESS_KEY_SECRET", "DISPATCHER_OSS_KEY_PREFIX"} {
t.Setenv(key, "legacy-value")
}
t.Setenv("DISPATCHER_OSS_GRANT_TTL_SECONDS", "1")
cfg, err := FromEnv()
if err != nil {
t.Fatal(err)
}
if cfg.OSSRegion != "" || cfg.OSSEndpoint != "" || cfg.OSSBucket != "" || cfg.OSSAccessKeyID != "" || cfg.OSSAccessKeySecret != "" || cfg.OSSKeyPrefix != "" || cfg.OSSGrantTTL != 0 {
t.Fatal("legacy environment still supplies OSS settings before the required JSON file")
}
}