@@ -3,9 +3,9 @@ package main
3
3
import (
4
4
"context"
5
5
"encoding/json"
6
- "fmt"
7
6
"log"
8
7
"os"
8
+ "path/filepath"
9
9
"strings"
10
10
11
11
"github.com/containerd/platforms"
@@ -20,7 +20,7 @@ import (
20
20
"github.com/pkg/errors"
21
21
)
22
22
23
- func getDockerfileDeps (dockerfile string , targetArch string ) string {
23
+ func getDockerfileDeps (dockerfile string , targetArch string ) [] string {
24
24
ctx := context .Background ()
25
25
data := noErr2 (os .ReadFile (dockerfile ))
26
26
@@ -47,42 +47,69 @@ func getDockerfileDeps(dockerfile string, targetArch string) string {
47
47
return getOpSourceFollowPaths (definition )
48
48
}
49
49
50
- func getOpSourceFollowPaths (definition * llb.Definition ) string {
50
+ func getOpSourceFollowPaths (definition * llb.Definition ) [] string {
51
51
// https://earthly.dev/blog/compiling-containers-dockerfiles-llvm-and-buildkit/
52
52
// https://stackoverflow.com/questions/73067660/what-exactly-is-the-frontend-and-backend-of-docker-buildkit
53
53
54
- ops := make ([ ]llbOp , 0 )
54
+ opsByDigest := make (map [digest. Digest ]llbOp , len ( definition . Def ) )
55
55
for _ , dt := range definition .Def {
56
56
var op pb.Op
57
57
if err := op .UnmarshalVT (dt ); err != nil {
58
58
panic ("failed to parse op" )
59
59
}
60
60
dgst := digest .FromBytes (dt )
61
- ent := llbOp {Op : & op , Digest : dgst , OpMetadata : definition .Metadata [dgst ].ToPB ()}
62
- ops = append (ops , ent )
61
+ ent := llbOp {
62
+ Op : & op ,
63
+ Digest : dgst ,
64
+ OpMetadata : definition .Metadata [dgst ].ToPB (),
65
+ }
66
+ opsByDigest [dgst ] = ent
63
67
}
64
68
65
- var result string = ""
66
- for _ , op := range ops {
67
- switch op := op .Op .Op .(type ) {
68
- case * pb.Op_Source :
69
- if strings .HasPrefix (op .Source .Identifier , "docker-image://" ) {
70
- // no-op
71
- } else if strings .HasPrefix (op .Source .Identifier , "local://" ) {
72
- paths := op .Source .Attrs [pb .AttrFollowPaths ]
73
- // TODO treat result as a set of strings to get unique set across all layers
74
- // this code "works" as is because it seems the terminal layer is the last one processed - which
75
- // contains all the referenced files - but treating this as a set seems safer (?)
76
- result = paths
77
- log .Printf ("found paths: %s" , paths )
78
- } else {
79
- panic (fmt .Errorf ("unexpected prefix %v" , op .Source .Identifier ))
69
+ var result []string
70
+ for _ , opDef := range opsByDigest {
71
+ switch top := opDef .Op .Op .(type ) {
72
+ // https://github.com/moby/buildkit/blob/v0.24/solver/pb/ops.proto#L308-L325
73
+ case * pb.Op_File :
74
+ for _ , a := range top .File .Actions {
75
+ // NOTE CAREFULLY: FileActionCopy copies files from secondaryInput on top of input
76
+ if cpy := a .GetCopy (); cpy != nil {
77
+ if inputIsFromLocalContext (a .SecondaryInput , opDef .Op .Inputs , opsByDigest ) {
78
+ result = append (result , cleanPath (cpy .Src ))
79
+ }
80
+ }
81
+ }
82
+ case * pb.Op_Exec :
83
+ for _ , m := range top .Exec .Mounts {
84
+ if inputIsFromLocalContext (m .Input , opDef .Op .Inputs , opsByDigest ) {
85
+ result = append (result , cleanPath (m .Selector ))
86
+ }
80
87
}
81
88
}
82
89
}
90
+
83
91
return result
84
92
}
85
93
94
+ func cleanPath (path string ) string {
95
+ return noErr2 (filepath .Rel ("/" , filepath .Clean (path )))
96
+ }
97
+
98
+ func inputIsFromLocalContext (input int64 , inputs []* pb.Input , opsByDigest map [digest.Digest ]llbOp ) bool {
99
+ // input is -1 if the input is a FROM scratch or equivalent
100
+ if input == - 1 {
101
+ return false
102
+ }
103
+
104
+ srcDigest := digest .Digest (inputs [input ].Digest )
105
+ sourceOp := opsByDigest [srcDigest ]
106
+ if src , ok := sourceOp .Op .Op .(* pb.Op_Source ); ok {
107
+ // local://context is the primary context, but there may be multiple named contexts
108
+ return strings .HasPrefix (src .Source .Identifier , "local://" )
109
+ }
110
+ return false
111
+ }
112
+
86
113
// llbOp holds data for a single loaded LLB op
87
114
type llbOp struct {
88
115
Op * pb.Op
0 commit comments