Skip to content

Commit af87f27

Browse files
committed
provenance: avoid intermediate wrapper for custom fields
While the custom fields used embedded Go struct field, this does not work for map types and needs custom JSON marshaller to make sure custom fields appear without wrapper. Signed-off-by: Tonis Tiigi <[email protected]>
1 parent fe65d5f commit af87f27

File tree

2 files changed

+167
-0
lines changed

2 files changed

+167
-0
lines changed

solver/llbsolver/provenance/types/types.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package types
22

33
import (
4+
"encoding/json"
5+
"maps"
46
"slices"
57

68
slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
@@ -311,3 +313,95 @@ func (p *ProvenancePredicateSLSA02) ConvertToSLSA1() *ProvenancePredicateSLSA1 {
311313
RunDetails: runDetails,
312314
}
313315
}
316+
317+
// MarshalJSON flattens ProvenanceCustomEnv into top level.
318+
func (p ProvenanceInternalParametersSLSA1) MarshalJSON() ([]byte, error) {
319+
type Alias ProvenanceInternalParametersSLSA1
320+
base, err := json.Marshal(Alias(p))
321+
if err != nil {
322+
return nil, err
323+
}
324+
var m map[string]any
325+
if err := json.Unmarshal(base, &m); err != nil {
326+
return nil, err
327+
}
328+
maps.Copy(m, p.ProvenanceCustomEnv)
329+
delete(m, "ProvenanceCustomEnv")
330+
return json.Marshal(m)
331+
}
332+
333+
// UnmarshalJSON fills both struct fields and flattened custom env.
334+
func (p *ProvenanceInternalParametersSLSA1) UnmarshalJSON(data []byte) error {
335+
var m map[string]any
336+
if err := json.Unmarshal(data, &m); err != nil {
337+
return err
338+
}
339+
340+
type Alias ProvenanceInternalParametersSLSA1
341+
var a Alias
342+
if err := json.Unmarshal(data, &a); err != nil {
343+
return err
344+
}
345+
346+
// Unmarshal known struct again to identify its keys
347+
structBytes, err := json.Marshal(a)
348+
if err != nil {
349+
return err
350+
}
351+
var known map[string]any
352+
if err := json.Unmarshal(structBytes, &known); err != nil {
353+
return err
354+
}
355+
356+
for k := range known {
357+
delete(m, k)
358+
}
359+
360+
*p = ProvenanceInternalParametersSLSA1(a)
361+
p.ProvenanceCustomEnv = m
362+
return nil
363+
}
364+
365+
func (p Environment) MarshalJSON() ([]byte, error) {
366+
type Alias Environment
367+
base, err := json.Marshal(Alias(p))
368+
if err != nil {
369+
return nil, err
370+
}
371+
var m map[string]any
372+
if err := json.Unmarshal(base, &m); err != nil {
373+
return nil, err
374+
}
375+
maps.Copy(m, p.ProvenanceCustomEnv)
376+
delete(m, "ProvenanceCustomEnv")
377+
return json.Marshal(m)
378+
}
379+
380+
func (p *Environment) UnmarshalJSON(data []byte) error {
381+
var m map[string]any
382+
if err := json.Unmarshal(data, &m); err != nil {
383+
return err
384+
}
385+
386+
type Alias Environment
387+
var a Alias
388+
if err := json.Unmarshal(data, &a); err != nil {
389+
return err
390+
}
391+
// Unmarshal known struct again to identify its keys
392+
structBytes, err := json.Marshal(a)
393+
if err != nil {
394+
return err
395+
}
396+
var known map[string]any
397+
if err := json.Unmarshal(structBytes, &known); err != nil {
398+
return err
399+
}
400+
401+
for k := range known {
402+
delete(m, k)
403+
}
404+
*p = Environment(a)
405+
p.ProvenanceCustomEnv = m
406+
return nil
407+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package types
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestMarsalBuildDefinitionSLSA1(t *testing.T) {
11+
inp := `{
12+
"buildType": "btype1",
13+
"externalParameters": {
14+
"configSource": {},
15+
"request": {}
16+
},
17+
"internalParameters": {
18+
"builderPlatform": "linux/amd64",
19+
"foo": "bar",
20+
"abc": 123,
21+
"def": {"one": 1}
22+
}
23+
}`
24+
25+
var def ProvenanceBuildDefinitionSLSA1
26+
err := json.Unmarshal([]byte(inp), &def)
27+
require.NoError(t, err)
28+
29+
require.Equal(t, "btype1", def.BuildType)
30+
require.Equal(t, "linux/amd64", def.InternalParameters.BuilderPlatform)
31+
require.Equal(t, "bar", def.InternalParameters.ProvenanceCustomEnv["foo"])
32+
require.InEpsilon(t, float64(123), def.InternalParameters.ProvenanceCustomEnv["abc"], 0.001)
33+
require.Equal(t, map[string]any{"one": float64(1)}, def.InternalParameters.ProvenanceCustomEnv["def"])
34+
35+
out, err := json.Marshal(def)
36+
require.NoError(t, err)
37+
38+
require.JSONEq(t, inp, string(out))
39+
}
40+
41+
func TestMarshalInvocation(t *testing.T) {
42+
inp := `{
43+
"configSource": {
44+
"uri": "git+https://github.com/example/repo.git"
45+
},
46+
"parameters": {
47+
"frontend": "dockerfile.v0"
48+
},
49+
"environment": {
50+
"platform": "linux/amd64",
51+
"buildkit": "v0.10.3",
52+
"custom": {
53+
"foo": "bar"
54+
},
55+
"bar": [1,2,3]
56+
}
57+
}`
58+
59+
var inv ProvenanceInvocationSLSA02
60+
err := json.Unmarshal([]byte(inp), &inv)
61+
require.NoError(t, err)
62+
63+
require.Equal(t, "git+https://github.com/example/repo.git", inv.ConfigSource.URI)
64+
require.Equal(t, "dockerfile.v0", inv.Parameters.Frontend)
65+
require.Equal(t, "linux/amd64", inv.Environment.Platform)
66+
require.Equal(t, "v0.10.3", inv.Environment.ProvenanceCustomEnv["buildkit"])
67+
require.Equal(t, "bar", inv.Environment.ProvenanceCustomEnv["custom"].(map[string]any)["foo"])
68+
require.Equal(t, []any{float64(1), float64(2), float64(3)}, inv.Environment.ProvenanceCustomEnv["bar"])
69+
out, err := json.Marshal(inv)
70+
require.NoError(t, err)
71+
72+
require.JSONEq(t, inp, string(out))
73+
}

0 commit comments

Comments
 (0)