@@ -57,10 +57,13 @@ type (
5757
5858 // Plugin defines the Docker plugin parameters.
5959 Plugin struct {
60- Login Login // Docker login configuration
61- Build Build // Docker build configuration
62- Dryrun bool // Docker push is skipped
63- Cleanup bool // Docker purge is enabled
60+ Login Login // Docker login configuration
61+ Build Build // Docker build configuration
62+ Dryrun bool // Docker push is skipped
63+ Cleanup bool // Docker purge is enabled
64+ PushOnly bool // Push only mode, skips build process
65+ SourceTarPath string // Path to Docker image tar file to load and push
66+ TarPath string // Path to save Docker image as tar file
6467 }
6568)
6669
@@ -105,6 +108,11 @@ func (p Plugin) Exec() error {
105108 fmt .Println ("Registry credentials or Docker config not provided. Guest mode enabled." )
106109 }
107110
111+ // Check if we're in push-only mode
112+ if p .PushOnly {
113+ return p .pushOnly ()
114+ }
115+
108116 // add proxy build args
109117 addProxyBuildArgs (& p .Build )
110118
@@ -122,11 +130,23 @@ func (p Plugin) Exec() error {
122130 for _ , tag := range p .Build .Tags {
123131 cmds = append (cmds , commandTag (p .Build , tag )) // docker tag
124132
125- if p .Dryrun == false {
133+ if ! p .Dryrun {
126134 cmds = append (cmds , commandPush (p .Build , tag )) // docker push
127135 }
128136 }
129137
138+ // If TarPath is specified and Dryrun is enabled, save the image to a tar file
139+ if p .TarPath != "" && p .Dryrun && len (p .Build .Tags ) > 0 {
140+ // Ensure parent directories exist
141+ if err := os .MkdirAll (filepath .Dir (p .TarPath ), 0755 ); err != nil {
142+ return fmt .Errorf ("failed to create parent directories for tar path: %s" , err )
143+ }
144+
145+ imageToSave := fmt .Sprintf ("%s:%s" , p .Build .Repo , p .Build .Tags [0 ])
146+ fmt .Println ("Saving image to tar:" , p .TarPath )
147+ cmds = append (cmds , commandSaveTar (imageToSave , p .TarPath ))
148+ }
149+
130150 if p .Cleanup {
131151 cmds = append (cmds , commandRmi (p .Build .Name )) // buildah rmi
132152 }
@@ -184,14 +204,14 @@ func commandLoginEmail(login Login) *exec.Cmd {
184204 )
185205}
186206
187- // helper function to create the docker info command.
207+ // helper function to create the docker version command.
188208func commandVersion () * exec.Cmd {
189209 return exec .Command (buildahExe , "version" )
190210}
191211
192212// helper function to create the docker info command.
193213func commandInfo () * exec.Cmd {
194- return exec .Command (buildahExe , "info" )
214+ return exec .Command (buildahExe , "--storage-driver" , "vfs" , " info" )
195215}
196216
197217// helper function to create the docker build command.
@@ -365,3 +385,134 @@ func commandRmi(tag string) *exec.Cmd {
365385func trace (cmd * exec.Cmd ) {
366386 fmt .Fprintf (os .Stdout , "+ %s\n " , strings .Join (cmd .Args , " " ))
367387}
388+
389+ // pushOnly handles pushing images without building them
390+ func (p Plugin ) pushOnly () error {
391+ // If source tar path is provided, load the image first
392+ if p .SourceTarPath != "" {
393+ fileInfo , err := os .Stat (p .SourceTarPath )
394+ if err != nil {
395+ if os .IsNotExist (err ) {
396+ return fmt .Errorf ("source image tar file %s does not exist" , p .SourceTarPath )
397+ }
398+ return fmt .Errorf ("failed to access source image tar file: %s" , err )
399+ }
400+
401+ if ! fileInfo .Mode ().IsRegular () {
402+ return fmt .Errorf ("source image tar %s is not a regular file" , p .SourceTarPath )
403+ }
404+
405+ fmt .Println ("Loading image from tar:" , p .SourceTarPath )
406+ loadCmd := commandLoadTar (p .SourceTarPath )
407+ loadCmd .Stdout = os .Stdout
408+ loadCmd .Stderr = os .Stderr
409+ trace (loadCmd )
410+ if err := loadCmd .Run (); err != nil {
411+ return fmt .Errorf ("failed to load image from tar: %s" , err )
412+ }
413+ }
414+
415+ // Check for required tags
416+ if len (p .Build .Tags ) == 0 {
417+ return fmt .Errorf ("no tags specified for push" )
418+ }
419+
420+ // Use the repository name as the source image name
421+ sourceImageName := p .Build .Repo
422+ sourceTags := p .Build .Tags
423+
424+ // For each source tag and target tag combination
425+ taggedForPush := make (map [string ]bool )
426+
427+ for _ , sourceTag := range sourceTags {
428+ sourceFullImageName := fmt .Sprintf ("%s:%s" , sourceImageName , sourceTag )
429+
430+ // Check if the source image exists in local storage
431+ existsCmd := commandImageExists (sourceFullImageName )
432+ existsCmd .Stdout = nil // suppress output, we only care about the exit code
433+ existsCmd .Stderr = os .Stderr
434+ trace (existsCmd )
435+
436+ if err := existsCmd .Run (); err != nil {
437+ fmt .Printf ("Warning: Source image %s not found\n " , sourceFullImageName )
438+ // Continue to the next source tag if available, otherwise return error
439+ if len (sourceTags ) > 1 {
440+ continue
441+ }
442+ return fmt .Errorf ("source image %s not found, cannot push" , sourceFullImageName )
443+ }
444+
445+ // For each target tag, tag and push
446+ for _ , targetTag := range p .Build .Tags {
447+ targetFullImageName := fmt .Sprintf ("%s:%s" , p .Build .Repo , targetTag )
448+
449+ // Skip if source and target are identical
450+ if sourceFullImageName == targetFullImageName {
451+ fmt .Printf ("Source and target image names are identical: %s\n " , sourceFullImageName )
452+ taggedForPush [targetFullImageName ] = true
453+ } else {
454+ // Tag the source image with the target name
455+ fmt .Printf ("Tagging %s as %s\n " , sourceFullImageName , targetFullImageName )
456+ tagCmd := exec .Command (buildahExe , "--storage-driver" , "vfs" , "tag" , sourceFullImageName , targetFullImageName )
457+ tagCmd .Stdout = os .Stdout
458+ tagCmd .Stderr = os .Stderr
459+ trace (tagCmd )
460+ if err := tagCmd .Run (); err == nil {
461+ taggedForPush [targetFullImageName ] = true
462+ } else {
463+ fmt .Printf ("Warning: Failed to tag %s as %s: %s\n " , sourceFullImageName , targetFullImageName , err )
464+ }
465+ }
466+ }
467+ }
468+
469+ // If no images were tagged or found, we can't proceed
470+ if len (taggedForPush ) == 0 {
471+ return fmt .Errorf ("no images found or tagged for repository %s, cannot push" , p .Build .Repo )
472+ }
473+
474+ var cmds []* exec.Cmd
475+
476+ // Push all tagged images
477+ for tag := range taggedForPush {
478+ // Extract tag from the full image name
479+ _ , tagOnly , found := strings .Cut (tag , ":" )
480+ if ! found {
481+ continue
482+ }
483+
484+ // Push the image if not in dry-run mode
485+ if ! p .Dryrun {
486+ cmds = append (cmds , commandPush (p .Build , tagOnly ))
487+ }
488+ }
489+
490+ // Execute all commands
491+ for _ , cmd := range cmds {
492+ cmd .Stdout = os .Stdout
493+ cmd .Stderr = os .Stderr
494+ trace (cmd )
495+ if err := cmd .Run (); err != nil {
496+ return fmt .Errorf ("command failed: %s" , err )
497+ }
498+ }
499+
500+ return nil
501+ }
502+
503+
504+
505+ // commandLoadTar creates a command to load an image from a tar file
506+ func commandLoadTar (tarPath string ) * exec.Cmd {
507+ return exec .Command (buildahExe , "--storage-driver" , "vfs" , "pull" , "docker-archive:" + tarPath )
508+ }
509+
510+ // commandImageExists creates a command to check if an image exists
511+ func commandImageExists (image string ) * exec.Cmd {
512+ return exec .Command (buildahExe , "inspect" , "--storage-driver" , "vfs" , "--type" , "image" , image )
513+ }
514+
515+ // commandSaveTar creates a command to save an image to a tar file
516+ func commandSaveTar (image string , tarPath string ) * exec.Cmd {
517+ return exec .Command (buildahExe , "push" , "--storage-driver" , "vfs" , image , "docker-archive:" + tarPath )
518+ }
0 commit comments