mirror of
https://github.com/moby/buildkit.git
synced 2026-06-30 19:57:39 +00:00
llbsolver: unmarshal protobuf objects into the provenance attestation correctly
This modifies how build steps are unmarshaled from JSON into the provenance attestation. The current method doesn't correctly handle protobuf attributes that are used with `oneof`. Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
This commit is contained in:
@@ -13,7 +13,7 @@ type BuildConfig struct {
|
||||
|
||||
type BuildStep struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Op interface{} `json:"op,omitempty"`
|
||||
Op pb.Op `json:"op,omitempty"`
|
||||
Inputs []string `json:"inputs,omitempty"`
|
||||
ResourceUsage *resourcestypes.Samples `json:"resourceUsage,omitempty"`
|
||||
}
|
||||
|
||||
96
solver/pb/json.go
Normal file
96
solver/pb/json.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package pb
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
func (m *Op) UnmarshalJSON(data []byte) error {
|
||||
var v struct {
|
||||
Inputs []*Input `json:"inputs,omitempty"`
|
||||
Op struct {
|
||||
*Op_Exec
|
||||
*Op_Source
|
||||
*Op_File
|
||||
*Op_Build
|
||||
*Op_Merge
|
||||
*Op_Diff
|
||||
}
|
||||
Platform *Platform `json:"platform,omitempty"`
|
||||
Constraints *WorkerConstraints `json:"constraints,omitempty"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.Inputs = v.Inputs
|
||||
switch {
|
||||
case v.Op.Op_Exec != nil:
|
||||
m.Op = v.Op.Op_Exec
|
||||
case v.Op.Op_Source != nil:
|
||||
m.Op = v.Op.Op_Source
|
||||
case v.Op.Op_File != nil:
|
||||
m.Op = v.Op.Op_File
|
||||
case v.Op.Op_Build != nil:
|
||||
m.Op = v.Op.Op_Build
|
||||
case v.Op.Op_Merge != nil:
|
||||
m.Op = v.Op.Op_Merge
|
||||
case v.Op.Op_Diff != nil:
|
||||
m.Op = v.Op.Op_Diff
|
||||
}
|
||||
m.Platform = v.Platform
|
||||
m.Constraints = v.Constraints
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *FileAction) UnmarshalJSON(data []byte) error {
|
||||
var v struct {
|
||||
Input InputIndex `json:"input"`
|
||||
SecondaryInput InputIndex `json:"secondaryInput"`
|
||||
Output OutputIndex `json:"output"`
|
||||
Action struct {
|
||||
*FileAction_Copy
|
||||
*FileAction_Mkfile
|
||||
*FileAction_Mkdir
|
||||
*FileAction_Rm
|
||||
}
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.Input = v.Input
|
||||
m.SecondaryInput = v.SecondaryInput
|
||||
m.Output = v.Output
|
||||
switch {
|
||||
case v.Action.FileAction_Copy != nil:
|
||||
m.Action = v.Action.FileAction_Copy
|
||||
case v.Action.FileAction_Mkfile != nil:
|
||||
m.Action = v.Action.FileAction_Mkfile
|
||||
case v.Action.FileAction_Mkdir != nil:
|
||||
m.Action = v.Action.FileAction_Mkdir
|
||||
case v.Action.FileAction_Rm != nil:
|
||||
m.Action = v.Action.FileAction_Rm
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *UserOpt) UnmarshalJSON(data []byte) error {
|
||||
var v struct {
|
||||
User struct {
|
||||
*UserOpt_ByName
|
||||
*UserOpt_ByID
|
||||
}
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
case v.User.UserOpt_ByName != nil:
|
||||
m.User = v.User.UserOpt_ByName
|
||||
case v.User.UserOpt_ByID != nil:
|
||||
m.User = v.User.UserOpt_ByID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
214
solver/pb/json_test.go
Normal file
214
solver/pb/json_test.go
Normal file
@@ -0,0 +1,214 @@
|
||||
package pb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOp_UnmarshalJSON(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
op *Op
|
||||
}{
|
||||
{
|
||||
name: "exec",
|
||||
op: &Op{
|
||||
Op: &Op_Exec{
|
||||
Exec: &ExecOp{
|
||||
Meta: &Meta{
|
||||
Args: []string{"echo", "Hello", "World"},
|
||||
},
|
||||
Mounts: []*Mount{
|
||||
{Input: 0, Dest: "/", Readonly: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "source",
|
||||
op: &Op{
|
||||
Op: &Op_Source{
|
||||
Source: &SourceOp{
|
||||
Identifier: "local://context",
|
||||
},
|
||||
},
|
||||
Constraints: &WorkerConstraints{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file",
|
||||
op: &Op{
|
||||
Op: &Op_File{
|
||||
File: &FileOp{
|
||||
Actions: []*FileAction{
|
||||
{
|
||||
Input: 1,
|
||||
Output: 2,
|
||||
Action: &FileAction_Copy{
|
||||
Copy: &FileActionCopy{
|
||||
Src: "/foo",
|
||||
Dest: "/bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "build",
|
||||
op: &Op{
|
||||
Op: &Op_Build{
|
||||
Build: &BuildOp{
|
||||
Def: &Definition{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "merge",
|
||||
op: &Op{
|
||||
Op: &Op_Merge{
|
||||
Merge: &MergeOp{
|
||||
Inputs: []*MergeInput{
|
||||
{Input: 0},
|
||||
{Input: 1},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "diff",
|
||||
op: &Op{
|
||||
Op: &Op_Diff{
|
||||
Diff: &DiffOp{
|
||||
Lower: &LowerDiffInput{Input: 0},
|
||||
Upper: &UpperDiffInput{Input: 1},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
out, err := json.Marshal(tt.op)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
exp, got := tt.op, &Op{}
|
||||
if err := json.Unmarshal(out, got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Equal(t, exp, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileAction_UnmarshalJSON(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
fileAction *FileAction
|
||||
}{
|
||||
{
|
||||
name: "copy",
|
||||
fileAction: &FileAction{
|
||||
Action: &FileAction_Copy{
|
||||
Copy: &FileActionCopy{
|
||||
Src: "/foo",
|
||||
Dest: "/bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mkfile",
|
||||
fileAction: &FileAction{
|
||||
Action: &FileAction_Mkfile{
|
||||
Mkfile: &FileActionMkFile{
|
||||
Path: "/foo",
|
||||
Data: []byte("Hello, World!"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mkdir",
|
||||
fileAction: &FileAction{
|
||||
Action: &FileAction_Mkdir{
|
||||
Mkdir: &FileActionMkDir{
|
||||
Path: "/foo/bar",
|
||||
MakeParents: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rm",
|
||||
fileAction: &FileAction{
|
||||
Action: &FileAction_Rm{
|
||||
Rm: &FileActionRm{
|
||||
Path: "/foo",
|
||||
AllowNotFound: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
out, err := json.Marshal(tt.fileAction)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
exp, got := tt.fileAction, &FileAction{}
|
||||
if err := json.Unmarshal(out, got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Equal(t, exp, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserOpt_UnmarshalJSON(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
userOpt *UserOpt
|
||||
}{
|
||||
{
|
||||
name: "byName",
|
||||
userOpt: &UserOpt{
|
||||
User: &UserOpt_ByName{
|
||||
ByName: &NamedUserOpt{
|
||||
Name: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "byId",
|
||||
userOpt: &UserOpt{
|
||||
User: &UserOpt_ByID{
|
||||
ByID: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
out, err := json.Marshal(tt.userOpt)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
exp, got := tt.userOpt, &UserOpt{}
|
||||
if err := json.Unmarshal(out, got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Equal(t, exp, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -151,6 +151,7 @@ func (CacheSharingOpt) EnumDescriptor() ([]byte, []int) {
|
||||
|
||||
// Op represents a vertex of the LLB DAG.
|
||||
type Op struct {
|
||||
// changes to this structure must be represented in json.go.
|
||||
// inputs is a set of input edges.
|
||||
Inputs []*Input `protobuf:"bytes,1,rep,name=inputs,proto3" json:"inputs,omitempty"`
|
||||
// Types that are valid to be assigned to Op:
|
||||
@@ -1961,6 +1962,7 @@ func (m *FileOp) GetActions() []*FileAction {
|
||||
}
|
||||
|
||||
type FileAction struct {
|
||||
// changes to this structure must be represented in json.go.
|
||||
Input InputIndex `protobuf:"varint,1,opt,name=input,proto3,customtype=InputIndex" json:"input"`
|
||||
SecondaryInput InputIndex `protobuf:"varint,2,opt,name=secondaryInput,proto3,customtype=InputIndex" json:"secondaryInput"`
|
||||
Output OutputIndex `protobuf:"varint,3,opt,name=output,proto3,customtype=OutputIndex" json:"output"`
|
||||
@@ -2482,6 +2484,8 @@ func (m *ChownOpt) GetGroup() *UserOpt {
|
||||
}
|
||||
|
||||
type UserOpt struct {
|
||||
// changes to this structure must be represented in json.go.
|
||||
//
|
||||
// Types that are valid to be assigned to User:
|
||||
//
|
||||
// *UserOpt_ByName
|
||||
|
||||
@@ -10,6 +10,7 @@ option (gogoproto.stable_marshaler_all) = true;
|
||||
|
||||
// Op represents a vertex of the LLB DAG.
|
||||
message Op {
|
||||
// changes to this structure must be represented in json.go.
|
||||
// inputs is a set of input edges.
|
||||
repeated Input inputs = 1;
|
||||
oneof op {
|
||||
@@ -288,6 +289,7 @@ message FileOp {
|
||||
}
|
||||
|
||||
message FileAction {
|
||||
// changes to this structure must be represented in json.go.
|
||||
int64 input = 1 [(gogoproto.customtype) = "InputIndex", (gogoproto.nullable) = false]; // could be real input or target (target index + max input index)
|
||||
int64 secondaryInput = 2 [(gogoproto.customtype) = "InputIndex", (gogoproto.nullable) = false]; // --//--
|
||||
int64 output = 3 [(gogoproto.customtype) = "OutputIndex", (gogoproto.nullable) = false];
|
||||
@@ -373,6 +375,7 @@ message ChownOpt {
|
||||
}
|
||||
|
||||
message UserOpt {
|
||||
// changes to this structure must be represented in json.go.
|
||||
oneof user {
|
||||
NamedUserOpt byName = 1;
|
||||
uint32 byID = 2;
|
||||
|
||||
Reference in New Issue
Block a user