diff --git a/.golangci.yml b/.golangci.yml index 8b0c09bb2..e074e7a5c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -33,12 +33,19 @@ linters: deny: - pkg: github.com/docker/cagent/internal desc: don't use /internal/ from /pkg/ + old_yaml: + files: + - "**/*.go" + deny: + - pkg: gopkg.in/yaml.v3 + desc: don't use deprecated gopkg.in/yaml.v3, use github.com/goccy/go-yaml instead testify_is_for_tests: files: - "**/*.go" - "!**/*_test.go" deny: - pkg: github.com/stretchr/testify + desc: don't use testify in production code gocritic: disabled-checks: - dupImport diff --git a/go.mod b/go.mod index 252748b3f..1546c3f65 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/charmbracelet/glamour/v2 v2.0.0-20250811143442-a27abb32f018 github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250721205738-ea66aa652ee0 github.com/fatih/color v1.18.0 + github.com/goccy/go-yaml v1.18.0 github.com/google/go-containerregistry v0.20.6 github.com/google/uuid v1.6.0 github.com/labstack/echo/v4 v4.13.4 @@ -25,7 +26,6 @@ require ( go.opentelemetry.io/otel/trace v1.38.0 golang.org/x/term v0.34.0 google.golang.org/genai v1.22.0 - gopkg.in/yaml.v3 v3.0.1 modernc.org/sqlite v1.39.0 ) @@ -121,6 +121,7 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect google.golang.org/grpc v1.75.0 // indirect google.golang.org/protobuf v1.36.8 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.66.3 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect diff --git a/go.sum b/go.sum index 44b18bbcf..f2f7604d9 100644 --- a/go.sum +++ b/go.sum @@ -91,6 +91,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= diff --git a/pkg/config/config.go b/pkg/config/config.go index 8a4037f8a..ca137e98a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -9,7 +9,7 @@ import ( v0 "github.com/docker/cagent/pkg/config/v0" v1 "github.com/docker/cagent/pkg/config/v1" latest "github.com/docker/cagent/pkg/config/v2" - "gopkg.in/yaml.v3" + "github.com/goccy/go-yaml" ) // LoadConfigSecure loads the configuration from a file with path validation @@ -80,22 +80,22 @@ func ValidatePathInDirectory(path, allowedDir string) (string, error) { func loadConfig(path string) (*latest.Config, error) { data, err := os.ReadFile(path) if err != nil { - return nil, fmt.Errorf("failed to read config file: %w", err) + return nil, fmt.Errorf("reading config file: %w", err) } var raw map[string]any if err := yaml.Unmarshal(data, &raw); err != nil { - return nil, fmt.Errorf("failed to parse config file %s: %w", path, err) + return nil, fmt.Errorf("parsing config file %s\n%s", path, yaml.FormatError(err, true, true)) } oldConfig, err := parseCurrentVersion(data, raw["version"]) if err != nil { - return nil, fmt.Errorf("failed to parse config file %s: %w", path, err) + return nil, fmt.Errorf("parsing config file %s\n%s", path, yaml.FormatError(err, true, true)) } config, err := migrateToLatestConfig(oldConfig) if err != nil { - return nil, fmt.Errorf("failed to migrate config: %w", err) + return nil, fmt.Errorf("migrating config: %w", err) } if err := validateConfig(&config); err != nil { diff --git a/pkg/config/v0/load.go b/pkg/config/v0/load.go index c2eb7fec6..9d20bfbe4 100644 --- a/pkg/config/v0/load.go +++ b/pkg/config/v0/load.go @@ -1,20 +1,9 @@ package v0 -import ( - "bytes" - - "gopkg.in/yaml.v3" -) +import "github.com/goccy/go-yaml" func Load(data []byte) (Config, error) { var cfg Config - - decoder := yaml.NewDecoder(bytes.NewReader(data)) - decoder.KnownFields(true) - err := decoder.Decode(&cfg) - if err != nil { - return cfg, err - } - - return cfg, nil + err := yaml.UnmarshalWithOptions(data, &cfg, yaml.Strict()) + return cfg, err } diff --git a/pkg/config/v1/load.go b/pkg/config/v1/load.go index 934d55d68..83a98f128 100644 --- a/pkg/config/v1/load.go +++ b/pkg/config/v1/load.go @@ -1,20 +1,9 @@ package v1 -import ( - "bytes" - - "gopkg.in/yaml.v3" -) +import "github.com/goccy/go-yaml" func Load(data []byte) (Config, error) { var cfg Config - - decoder := yaml.NewDecoder(bytes.NewReader(data)) - decoder.KnownFields(true) - err := decoder.Decode(&cfg) - if err != nil { - return cfg, err - } - - return cfg, nil + err := yaml.UnmarshalWithOptions(data, &cfg, yaml.Strict()) + return cfg, err } diff --git a/pkg/config/v2/load.go b/pkg/config/v2/load.go index 28a02dff8..0e1528a5a 100644 --- a/pkg/config/v2/load.go +++ b/pkg/config/v2/load.go @@ -1,20 +1,9 @@ package v2 -import ( - "bytes" - - "gopkg.in/yaml.v3" -) +import "github.com/goccy/go-yaml" func Load(data []byte) (Config, error) { var cfg Config - - decoder := yaml.NewDecoder(bytes.NewReader(data)) - decoder.KnownFields(true) - err := decoder.Decode(&cfg) - if err != nil { - return cfg, err - } - - return cfg, nil + err := yaml.UnmarshalWithOptions(data, &cfg, yaml.Strict()) + return cfg, err } diff --git a/pkg/gateway/catalog.go b/pkg/gateway/catalog.go index 4a68fbc38..1864c79d8 100644 --- a/pkg/gateway/catalog.go +++ b/pkg/gateway/catalog.go @@ -7,7 +7,7 @@ import ( "net/http" "strings" - "gopkg.in/yaml.v3" + "github.com/goccy/go-yaml" ) const DockerCatalogURL = "https://desktop.docker.com/mcp/catalog/v2/catalog.yaml" diff --git a/pkg/server/server.go b/pkg/server/server.go index 0d6d7c431..5b0422d9b 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -18,10 +18,6 @@ import ( "time" "dario.cat/mergo" - "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" - "gopkg.in/yaml.v3" - "github.com/docker/cagent/pkg/api" "github.com/docker/cagent/pkg/config" latest "github.com/docker/cagent/pkg/config/v2" @@ -35,6 +31,9 @@ import ( "github.com/docker/cagent/pkg/session" "github.com/docker/cagent/pkg/team" "github.com/docker/cagent/pkg/teamloader" + "github.com/goccy/go-yaml" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" ) type Server struct { @@ -218,16 +217,11 @@ func (s *Server) editAgentConfig(c echo.Context) error { } // Marshal the merged configuration to YAML - var buf bytes.Buffer - encoder := yaml.NewEncoder(&buf) - encoder.SetIndent(2) - err = encoder.Encode(mergedConfig) + yamlData, err := yaml.MarshalWithOptions(mergedConfig, yaml.Indent(2)) if err != nil { slog.Error("Failed to marshal merged config to YAML", "error", err) return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to generate merged YAML configuration"}) } - encoder.Close() - yamlData := buf.Bytes() // Combine shebang, version, and merged YAML content finalContent := shebang + versionLine @@ -346,16 +340,11 @@ func (s *Server) createAgentConfig(c echo.Context) error { } // Marshal to YAML with custom indentation (2 spaces) - var buf bytes.Buffer - encoder := yaml.NewEncoder(&buf) - encoder.SetIndent(1) - err := encoder.Encode(agentConfig) + yamlData, err := yaml.MarshalWithOptions(agentConfig, yaml.Indent(2)) if err != nil { slog.Error("Failed to marshal agent config to YAML", "error", err) return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to generate YAML configuration"}) } - encoder.Close() - yamlData := buf.Bytes() // Prepend shebang line to the YAML content shebang := "#!/usr/bin/env cagent run\nversion: \"1\"\n\n" diff --git a/pkg/tools/mcp/gateway.go b/pkg/tools/mcp/gateway.go index edb40ad06..cf65c2df0 100644 --- a/pkg/tools/mcp/gateway.go +++ b/pkg/tools/mcp/gateway.go @@ -12,7 +12,7 @@ import ( "github.com/docker/cagent/pkg/environment" "github.com/docker/cagent/pkg/gateway" "github.com/docker/cagent/pkg/tools" - "gopkg.in/yaml.v3" + "github.com/goccy/go-yaml" ) const ENV_DOCKER_MCP_URL_PREFIX = "DOCKER_MCP_URL_"