@@ -27,6 +27,7 @@ import (
27
27
v1 "github.com/google/go-containerregistry/pkg/v1"
28
28
"github.com/google/go-containerregistry/pkg/v1/daemon"
29
29
"github.com/google/go-containerregistry/pkg/v1/empty"
30
+ "github.com/google/go-containerregistry/pkg/v1/layout"
30
31
"github.com/google/go-containerregistry/pkg/v1/mutate"
31
32
"github.com/google/go-containerregistry/pkg/v1/remote"
32
33
"github.com/google/go-containerregistry/pkg/v1/tarball"
@@ -79,6 +80,7 @@ type buildParams struct {
79
80
staticFiles map [string ]string
80
81
imageRefs []name.Tag
81
82
publish bool
83
+ outPath string
82
84
ldflags string
83
85
gotags string
84
86
target string
@@ -96,6 +98,7 @@ func main() {
96
98
ldflagsArg = flag .String ("ldflags" , "" , "the --ldflags value to pass to go" )
97
99
gotags = flag .String ("gotags" , "" , "the --tags value to pass to go" )
98
100
push = flag .Bool ("push" , false , "publish the image" )
101
+ outPath = flag .String ("out-path" , "" , "write single images to a tarball, or multi-platform images to a folder" )
99
102
target = flag .String ("target" , "" , "build for a specific env (options: flyio, local)" )
100
103
verbose = flag .Bool ("v" , false , "verbose build output" )
101
104
annotations = flag .String ("annotations" , "" , `OCI image annotations https://github.com/opencontainers/image-spec/blob/main/annotations.md.
@@ -140,6 +143,7 @@ func main() {
140
143
staticFiles : staticFiles ,
141
144
imageRefs : refs ,
142
145
publish : * push ,
146
+ outPath : * outPath ,
143
147
ldflags : * ldflagsArg ,
144
148
gotags : * gotags ,
145
149
target : * target ,
@@ -198,6 +202,17 @@ func verifyPlatform(p v1.Platform, target string) error {
198
202
return nil
199
203
}
200
204
205
+ func writeImageToFile (img v1.Image , imgRef name.Reference , path string ) error {
206
+ if ! strings .HasSuffix (path , ".tar" ) {
207
+ return fmt .Errorf ("out-path must end with .tar for single image, was: %s" , path )
208
+ }
209
+ if err := tarball .WriteToFile (path , imgRef , img ); err != nil {
210
+ return err
211
+ }
212
+
213
+ return nil
214
+ }
215
+
201
216
func fetchAndBuild (bp * buildParams ) error {
202
217
ctx := context .Background ()
203
218
logf := log .Printf
@@ -243,25 +258,30 @@ func fetchAndBuild(bp *buildParams) error {
243
258
if err != nil {
244
259
return err
245
260
}
246
- if ! bp .publish {
247
- logf ("not pushing" )
248
- return nil
249
- }
250
261
251
- img = mutate .Annotations (img , bp .annotations ).(v1.Image ) // OCI annotations
262
+ switch {
263
+ case bp .publish :
264
+ img = mutate .Annotations (img , bp .annotations ).(v1.Image ) // OCI annotations
252
265
253
- for _ , r := range bp .imageRefs {
254
- if bp .target == "local" {
255
- if err := loadLocalImage (logf , r , img ); err != nil {
266
+ for _ , r := range bp .imageRefs {
267
+ if bp .target == "local" {
268
+ if err := loadLocalImage (logf , r , img ); err != nil {
269
+ return err
270
+ }
271
+ continue
272
+ }
273
+ logf ("pushing to %v" , r )
274
+ if err := remote .Write (r , img , remoteOpts ... ); err != nil {
256
275
return err
257
276
}
258
- continue
259
- }
260
- logf ("pushing to %v" , r )
261
- if err := remote .Write (r , img , remoteOpts ... ); err != nil {
262
- return err
263
277
}
278
+ return nil
279
+
280
+ case bp .outPath != "" :
281
+ return writeImageToFile (img , bp .imageRefs [0 ], bp .outPath )
264
282
}
283
+ logf ("not pushing or writing to file" )
284
+
265
285
return nil
266
286
case types .OCIImageIndex , types .DockerManifestList :
267
287
// baseRef is a multi-platform index, rest of the method handles this.
@@ -336,23 +356,28 @@ func fetchAndBuild(bp *buildParams) error {
336
356
return err
337
357
}
338
358
logf ("image digest: %v" , d )
339
- if ! bp .publish {
340
- logf ("not pushing" )
341
- return nil
342
- }
343
359
344
- for _ , r := range bp .imageRefs {
345
- if bp .target == "local" {
346
- if err := loadLocalImage (logf , r , img ); err != nil {
360
+ switch {
361
+ case bp .publish :
362
+ for _ , r := range bp .imageRefs {
363
+ if bp .target == "local" {
364
+ if err := loadLocalImage (logf , r , img ); err != nil {
365
+ return err
366
+ }
367
+ continue
368
+ }
369
+ logf ("pushing to %v" , r )
370
+ if err := remote .Write (r , img , remoteOpts ... ); err != nil {
347
371
return err
348
372
}
349
- continue
350
- }
351
- logf ("pushing to %v" , r )
352
- if err := remote .Write (r , img , remoteOpts ... ); err != nil {
353
- return err
354
373
}
374
+ return nil
375
+
376
+ case bp .outPath != "" :
377
+ return writeImageToFile (img , bp .imageRefs [0 ], bp .outPath )
355
378
}
379
+ logf ("not pushing or writing to file" )
380
+
356
381
return nil
357
382
}
358
383
if bp .target == "local" {
@@ -371,17 +396,35 @@ func fetchAndBuild(bp *buildParams) error {
371
396
idx = mutate .Annotations (idx , bp .annotations ).(v1.ImageIndex )
372
397
373
398
logf ("index digest: %v" , d )
374
- if ! bp .publish {
375
- logf ("not pushing" )
399
+
400
+ switch {
401
+ case bp .publish :
402
+ for _ , r := range bp .imageRefs {
403
+ logf ("pushing to %v" , r )
404
+ if err := remote .WriteIndex (r , idx , remoteOpts ... ); err != nil {
405
+ return err
406
+ }
407
+ }
408
+
376
409
return nil
377
- }
378
410
379
- for _ , r := range bp .imageRefs {
380
- logf ("pushing to %v" , r )
381
- if err := remote .WriteIndex (r , idx , remoteOpts ... ); err != nil {
411
+ case bp .outPath != "" :
412
+ fi , err := os .Stat (bp .outPath )
413
+ if err != nil {
414
+ if ! os .IsNotExist (err ) {
415
+ return fmt .Errorf ("checking out path: %w" , err )
416
+ }
417
+ }
418
+ if fi != nil && ! fi .IsDir () {
419
+ return fmt .Errorf ("out-path must be a directory for multi-platform images: %s" , bp .outPath )
420
+ }
421
+ if _ , err := layout .Write (bp .outPath , idx ); err != nil {
382
422
return err
383
423
}
424
+
425
+ return nil
384
426
}
427
+ logf ("not pushing or writing to file" )
385
428
386
429
return nil
387
430
}
0 commit comments