Skip to content

[bug] generate fails when multiple patch templates are applied and an earlier template sets a tmpfs.size value #470

@Venkatesh1505

Description

@Venkatesh1505

Describe the bug

When multiple patch templates are loaded (via --patch-templates specified multiple times or via a directory), generate fails with a JSON unmarshal error if any earlier template sets a value that uses types.UnitBytes (e.g., tmpfs.size).

Error: failed to patch template 2: failed to unmarshal patched output: json: cannot unmarshal string
into Go struct field ServiceVolumeTmpfs.services.volumes.tmpfs.size of type types.UnitBytes

A single patch template works fine. The bug only triggers when a second template is applied after the first.

To Reproduce

  1. Create a minimal score.yaml:

    apiVersion: score.dev/v1b1
    metadata:
      name: example
    containers:
      hello-world:
        image: nginx:latest
    service:
      ports:
        www:
          port: 8080
          targetPort: 80
  2. Init with two patch templates where the first sets a tmpfs volume (e.g., using community patchers):

    score-compose init --no-sample \
      --patch-templates ../community-patchers/score-compose/microcks.tpl \
      --patch-templates ../community-patchers/score-compose/ollama.tpl
  3. Run generate:

    score-compose generate score.yaml
  4. See error:

    Error: failed to patch template 2: failed to unmarshal patched output: json: cannot unmarshal string
    into Go struct field ServiceVolumeTmpfs.services.volumes.tmpfs.size of type types.UnitBytes
    

Expected behavior

Both patch templates should be applied successfully and compose.yaml should be generated with both microcks and ollama services.

Screenshots

Running with -vv shows template 1 succeeds but template 2 fails during unmarshal:

Applying patching template 1
Applying patch  operation=set  path=services.microcks  value="map[...volumes:[map[target:/tmp tmpfs:map[size:655360] type:tmpfs]]]"

Applying patching template 2
Applying patch  operation=set  path=services.ollama  value="map[image:ollama/ollama:latest ...]"
Applying patch  operation=set  path=volumes.ollama_data  value="map[driver:local name:ollama_data]"

Error: failed to patch template 2: failed to unmarshal patched output: json: cannot unmarshal string
into Go struct field ServiceVolumeTmpfs.services.volumes.tmpfs.size of type types.UnitBytes

Note that template 1's patch sets size:655360 as a numeric value. The error occurs when template 2 tries to unmarshal the result back into *types.Project - by that point, the size value has been corrupted from a number to a string during the YAML round-trip between the two templates.

Desktop (please complete the following information):

  • OS: macOS
  • Version: built from source (main branch)

Additional context

Root cause analysis:

PatchServices (internal/patching/patching.go) is called once per template in a loop (internal/command/generate.go:293-298). Each call performs:

  1. yamlRoundTrip: *types.Projectyaml.Marshalyaml.Unmarshal into map[string]interface{}
  2. json.Marshal the untyped map
  3. Apply patches via sjson
  4. json.Unmarshal back into *types.Project

The corruption happens at step 1 on the second template. types.UnitBytes in compose-go (types/bytes.go) has a custom MarshalYAML that serializes the numeric value as a string:

func (u UnitBytes) MarshalYAML() (interface{}, error) {
    return fmt.Sprintf("%d", u), nil  // returns "655360" (string, not int)
}

When this YAML is unmarshaled into map[string]interface{}, the value becomes a Go string("655360") instead of a number. This string flows through json.Marshalsjsonjson.Unmarshal, and the final unmarshal into *types.Project fails because UnitBytes is type UnitBytes int64 with no custom UnmarshalJSON — Go's default int64 unmarshaler rejects the string.

Similarly, MarshalJSON also serializes as a quoted string:

func (u UnitBytes) MarshalJSON() ([]byte, error) {
    return []byte(fmt.Sprintf(`"%d"`, u)), nil  // returns "655360" (string in JSON)
}

The type has a DecodeMapstructure method that handles both int and string, but that's only used by the mapstructure library (compose-go's internal loading path), not by encoding/json or gopkg.in/yaml.v3.

This asymmetry was introduced in compose-spec/compose-go#482 to fix docker/compose#11160.

Suggested fix:

The fix should be upstream in compose-go - add UnmarshalJSON and UnmarshalYAML to types.UnitBytes that accept both numeric and string values, mirroring the existing DecodeMapstructure logic.

Metadata

Metadata

Labels

bugSomething isn't workingdiscussiondockerPull requests that update Docker codegoPull requests that update Go codequestionFurther information is requested

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions