@@ -19,13 +19,15 @@ package docker
19
19
import (
20
20
"bytes"
21
21
"context"
22
+ "encoding/json"
22
23
"fmt"
23
24
"io"
24
25
"os"
25
26
"os/exec"
26
27
27
28
v1 "github.com/google/go-containerregistry/pkg/v1"
28
29
30
+ "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/config"
29
31
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/docker"
30
32
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/instrumentation"
31
33
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/output"
@@ -46,7 +48,7 @@ func (b *Builder) Build(ctx context.Context, out io.Writer, a *latest.Artifact,
46
48
if len (matcher .Platforms ) == 1 {
47
49
pl = util .ConvertToV1Platform (matcher .Platforms [0 ])
48
50
}
49
- a = adjustCacheFrom ( a , tag )
51
+ a = b . adjustCache ( ctx , a , tag )
50
52
instrumentation .AddAttributesToCurrentSpanFromContext (ctx , map [string ]string {
51
53
"BuildType" : "docker" ,
52
54
"Context" : instrumentation .PII (a .Workspace ),
@@ -82,7 +84,7 @@ func (b *Builder) Build(ctx context.Context, out io.Writer, a *latest.Artifact,
82
84
return "" , newBuildError (err , b .cfg )
83
85
}
84
86
85
- if b .pushImages {
87
+ if ! b . useCLI && b .pushImages {
86
88
// TODO (tejaldesai) Remove https://github.com/GoogleContainerTools/skaffold/blob/main/pkg/skaffold/errors/err_map.go#L56
87
89
// and instead define a pushErr() method here.
88
90
return b .localDocker .Push (ctx , out , tag )
@@ -120,10 +122,31 @@ func (b *Builder) dockerCLIBuild(ctx context.Context, out io.Writer, name string
120
122
args = append (args , "--platform" , pl .String ())
121
123
}
122
124
123
- if b .useBuildKit != nil && * b .useBuildKit && ! b .pushImages {
124
- args = append (args , "--load" )
125
+ if b .useBuildKit != nil && * b .useBuildKit {
126
+ if ! b .pushImages {
127
+ load := true
128
+ if b .buildx {
129
+ // if docker daemon is not used, do not try to load the image (a buildx warning will be logged)
130
+ _ , err := b .localDocker .ServerVersion (ctx )
131
+ load = err == nil
132
+ }
133
+ if load {
134
+ args = append (args , "--load" )
135
+ }
136
+ } else if b .buildx {
137
+ // with buildx, push the image directly to the registry (not using the docker daemon)
138
+ args = append (args , "--push" )
139
+ }
125
140
}
126
141
142
+ // temporary file for buildx metadata containing the image digest:
143
+ var metadata * os.File
144
+ if b .buildx {
145
+ metadata , err = os .CreateTemp ("" , "metadata.json" )
146
+ metadata .Close ()
147
+ defer os .Remove (metadata .Name ())
148
+ args = append (args , "--metadata-file" , metadata .Name ())
149
+ }
127
150
cmd := exec .CommandContext (ctx , "docker" , args ... )
128
151
cmd .Env = append (util .OSEnviron (), b .localDocker .ExtraEnv ()... )
129
152
if b .useBuildKit != nil {
@@ -146,11 +169,16 @@ func (b *Builder) dockerCLIBuild(ctx context.Context, out io.Writer, name string
146
169
return "" , tryExecFormatErr (fmt .Errorf ("running build: %w" , err ), errBuffer )
147
170
}
148
171
149
- return b .localDocker .ImageID (ctx , opts .Tag )
172
+ if ! b .buildx {
173
+ return b .localDocker .ImageID (ctx , opts .Tag )
174
+ } else {
175
+ return getBuildxDigest (ctx , metadata .Name ())
176
+ }
150
177
}
151
178
152
179
func (b * Builder ) pullCacheFromImages (ctx context.Context , out io.Writer , a * latest.DockerArtifact , pl v1.Platform ) error {
153
- if len (a .CacheFrom ) == 0 {
180
+ // when using buildx, avoid pulling as the builder not necessarily uses the local docker daemon
181
+ if len (a .CacheFrom ) == 0 || b .buildx {
154
182
return nil
155
183
}
156
184
@@ -172,26 +200,81 @@ func (b *Builder) pullCacheFromImages(ctx context.Context, out io.Writer, a *lat
172
200
return nil
173
201
}
174
202
175
- // adjustCacheFrom returns an artifact where any cache references from the artifactImage is changed to the tagged built image name instead.
176
- func adjustCacheFrom (a * latest.Artifact , artifactTag string ) * latest.Artifact {
203
+ // adjustCache returns an artifact where any cache references from the artifactImage is changed to the tagged built image name instead.
204
+ // Under buildx, if cache-tag is configured, it will be used instead of the generated artifact tag (registry preserved)
205
+ // if no cacheTo was specified in the skaffold yaml, it will add a tagged destination using the same cache source reference
206
+ func (b * Builder ) adjustCache (ctx context.Context , a * latest.Artifact , artifactTag string ) * latest.Artifact {
207
+ cacheRef := a .ImageName // lookup value to be replaced
208
+ cacheTag := artifactTag // full reference to be used
177
209
if os .Getenv ("SKAFFOLD_DISABLE_DOCKER_CACHE_ADJUSTMENT" ) != "" {
178
210
// allow this behaviour to be disabled
179
211
return a
180
212
}
181
-
182
- if ! stringslice .Contains (a .DockerArtifact .CacheFrom , a .ImageName ) {
213
+ if b .buildx {
214
+ // compute the full cache reference (including registry, preserving tag)
215
+ tag , _ := config .GetCacheTag (b .cfg .GlobalConfig ())
216
+ imgRef , err := docker .ParseReference (artifactTag )
217
+ if err != nil {
218
+ log .Entry (ctx ).Errorf ("couldn't parse image tag: %w" , err )
219
+ } else if tag != "" {
220
+ cacheTag = fmt .Sprintf ("%s/%s:%s" , imgRef .Repo , cacheRef , tag )
221
+ }
222
+ }
223
+ if ! stringslice .Contains (a .DockerArtifact .CacheFrom , cacheRef ) {
183
224
return a
184
225
}
185
226
186
227
cf := make ([]string , 0 , len (a .DockerArtifact .CacheFrom ))
228
+ ct := make ([]string , 0 , len (a .DockerArtifact .CacheTo ))
187
229
for _ , image := range a .DockerArtifact .CacheFrom {
188
- if image == a .ImageName {
189
- cf = append (cf , artifactTag )
230
+ if image == cacheRef {
231
+ // change cache reference to to the tagged image name (built or given, including registry)
232
+ log .Entry (ctx ).Debugf ("Adjusting cache source image ref: %s" , cacheTag )
233
+ cf = append (cf , cacheTag )
234
+ if b .buildx {
235
+ // add cache destination reference, only if we're pushing to a registry and not given in config
236
+ if len (a .DockerArtifact .CacheTo ) == 0 && b .pushImages {
237
+ log .Entry (ctx ).Debugf ("Adjusting cache destination image ref: %s" , cacheTag )
238
+ ct = append (ct , fmt .Sprintf ("type=registry,ref=%s,mode=max" , cacheTag ))
239
+ }
240
+ }
190
241
} else {
191
242
cf = append (cf , image )
192
243
}
193
244
}
245
+ // just copy any other cache destination given in the config file:
246
+ for _ , image := range a .DockerArtifact .CacheTo {
247
+ ct = append (cf , image )
248
+ }
194
249
copy := * a
195
250
copy .DockerArtifact .CacheFrom = cf
251
+ copy .DockerArtifact .CacheTo = ct
196
252
return & copy
197
253
}
254
+
255
+ func getBuildxDigest (ctx context.Context , filename string ) (string , error ) {
256
+ var metadata map [string ]interface {}
257
+ data , err := os .ReadFile (filename )
258
+ if err == nil {
259
+ err = json .Unmarshal (data , & metadata )
260
+ }
261
+ if err == nil {
262
+ // avoid panic: interface conversion: interface {} is nil, not string (if keys don't exists)
263
+ var digest string
264
+ if value := metadata ["containerimage.digest" ]; value != nil {
265
+ digest = value .(string )
266
+ }
267
+ var name string
268
+ if value := metadata ["image.name" ]; value != nil {
269
+ name = value .(string )
270
+ }
271
+ if digest != "" {
272
+ log .Entry (ctx ).Debugf ("Image digest found in buildx metadata: %s for %s" , digest , name )
273
+ return digest , nil
274
+ }
275
+ }
276
+ log .Entry (ctx ).Warnf ("No digest found in buildx metadata: %w" , err )
277
+ // if image is not pushed, it could not contain the digest log for debugging:
278
+ log .Entry (ctx ).Debugf ("Full buildx metadata: %s" , data )
279
+ return "" , err
280
+ }
0 commit comments