@@ -112,6 +112,9 @@ type (
112112 BaseImageRegistry string // Docker registry to pull base image
113113 BaseImageUsername string // Docker registry username to pull base image
114114 BaseImagePassword string // Docker registry password to pull base image
115+ PushOnly bool // Push only mode, skips build process
116+ SourceTarPath string // Path to Docker image tar file to load and push
117+ TarPath string // Path to save Docker image as tar file
115118 }
116119
117120 Card []struct {
@@ -349,6 +352,11 @@ func (p Plugin) Exec() error {
349352 }()
350353 }
351354
355+ // Handle push-only mode if requested
356+ if p .PushOnly {
357+ return p .pushOnly ()
358+ }
359+
352360 // add proxy build args
353361 addProxyBuildArgs (& p .Build )
354362
@@ -358,7 +366,7 @@ func (p Plugin) Exec() error {
358366 cmds = append (cmds , commandInfo ()) // docker info
359367
360368 // Command to build, tag and push
361- cmds = append (cmds , commandBuildx (p .Build , p .Builder , p .Dryrun , p .MetadataFile )) // docker build
369+ cmds = append (cmds , commandBuildx (p .Build , p .Builder , p .Dryrun , p .MetadataFile , p . TarPath )) // docker build
362370
363371 // execute all commands in batch mode.
364372 for _ , cmd := range cmds {
@@ -410,6 +418,38 @@ func (p Plugin) Exec() error {
410418 }
411419 }
412420
421+ if p .TarPath != "" && p .Dryrun {
422+ if len (p .Build .Tags ) > 0 {
423+ tag := p .Build .Tags [0 ]
424+ fullImageName := fmt .Sprintf ("%s:%s" , p .Build .Repo , tag )
425+
426+ if ! imageExists (fullImageName ) {
427+ return fmt .Errorf ("error: image %s not found in local daemon, cannot save to tar" , fullImageName )
428+ }
429+
430+ // Make sure the directory exists
431+ dir := filepath .Dir (p .TarPath )
432+ if err := os .MkdirAll (dir , 0755 ); err != nil {
433+ return fmt .Errorf ("error: failed to create directory for tar file: %v" , err )
434+ }
435+
436+ // Save the image
437+ fmt .Println ("Saving image to tar:" , p .TarPath )
438+ saveCmd := commandSaveTar (fullImageName , p .TarPath )
439+ saveCmd .Stdout = os .Stdout
440+ saveCmd .Stderr = os .Stderr
441+ trace (saveCmd )
442+
443+ if err := saveCmd .Run (); err != nil {
444+ return fmt .Errorf ("error: failed to save image to tar: %v" , err )
445+ }
446+
447+ fmt .Printf ("Successfully saved image to %s\n " , p .TarPath )
448+ } else {
449+ return fmt .Errorf ("error: cannot save image to tar, no tags specified" )
450+ }
451+ }
452+
413453 // output the adaptive card
414454 if p .Builder .Driver == defaultDriver {
415455 if err := p .writeCard (); err != nil {
@@ -559,7 +599,7 @@ func commandInfo() *exec.Cmd {
559599}
560600
561601// helper function to create the docker buildx command.
562- func commandBuildx (build Build , builder Builder , dryrun bool , metadataFile string ) * exec.Cmd {
602+ func commandBuildx (build Build , builder Builder , dryrun bool , metadataFile string , tarPath string ) * exec.Cmd {
563603 args := []string {
564604 "buildx" ,
565605 "build" ,
@@ -576,7 +616,7 @@ func commandBuildx(build Build, builder Builder, dryrun bool, metadataFile strin
576616 args = append (args , "-t" , fmt .Sprintf ("%s:%s" , build .Repo , t ))
577617 }
578618 if dryrun {
579- if build .BuildxLoad {
619+ if build .BuildxLoad || tarPath != "" {
580620 args = append (args , "--load" )
581621 }
582622 } else {
@@ -882,6 +922,28 @@ func commandLoad() *exec.Cmd {
882922 return exec .Command (dockerExe , "image" , "load" )
883923}
884924
925+ func commandLoadTar (tarPath string ) * exec.Cmd {
926+ return exec .Command (dockerExe , "load" , "-i" , tarPath )
927+ }
928+
929+ func commandSaveTar (tag string , tarPath string ) * exec.Cmd {
930+ return exec .Command (dockerExe , "save" , "-o" , tarPath , tag )
931+ }
932+
933+ func imageExists (tag string ) bool {
934+ cmd := exec .Command (dockerExe , "image" , "inspect" , tag )
935+ return cmd .Run () == nil
936+ }
937+
938+ func getDigestAfterPush (tag string ) (string , error ) {
939+ cmd := exec .Command (dockerExe , "inspect" , "--format" , "{{ index (split (index .RepoDigests 0) \" @\" ) 1 }}" , tag )
940+ output , err := cmd .Output ()
941+ if err != nil {
942+ return "" , fmt .Errorf ("failed to get digest for %s: %w" , tag , err )
943+ }
944+ return strings .TrimSpace (string (output )), nil
945+ }
946+
885947func writeSSHPrivateKey (key string ) (path string , err error ) {
886948 home , err := os .UserHomeDir ()
887949 if err != nil {
@@ -913,3 +975,85 @@ func updateImageVersion(driverOpts *[]string, version string) {
913975 }
914976 }
915977}
978+
979+ // pushOnly handles pushing images without building them
980+ func (p Plugin ) pushOnly () error {
981+ // If source tar path is provided, load the image first
982+ if p .SourceTarPath != "" {
983+ fileInfo , err := os .Stat (p .SourceTarPath )
984+ if err != nil {
985+ if os .IsNotExist (err ) {
986+ return fmt .Errorf ("source image tar file %s does not exist" , p .SourceTarPath )
987+ }
988+ return fmt .Errorf ("failed to access source image tar file: %w" , err )
989+ }
990+
991+ if ! fileInfo .Mode ().IsRegular () {
992+ return fmt .Errorf ("source image tar %s is not a regular file" , p .SourceTarPath )
993+ }
994+
995+ fmt .Println ("Loading image from tar:" , p .SourceTarPath )
996+ loadCmd := commandLoadTar (p .SourceTarPath )
997+ loadCmd .Stdout = os .Stdout
998+ loadCmd .Stderr = os .Stderr
999+ trace (loadCmd )
1000+ if err := loadCmd .Run (); err != nil {
1001+ return fmt .Errorf ("failed to load image from tar: %w" , err )
1002+ }
1003+ }
1004+
1005+ // For each tag, verify image exists and push
1006+ var digest string
1007+ for _ , tag := range p .Build .Tags {
1008+ fullImageName := fmt .Sprintf ("%s:%s" , p .Build .Repo , tag )
1009+
1010+ // Check if image exists in local daemon
1011+ if ! imageExists (fullImageName ) {
1012+ return fmt .Errorf ("image %s not found, cannot push" , fullImageName )
1013+ }
1014+
1015+ // Push image
1016+ fmt .Println ("Pushing image:" , fullImageName )
1017+ pushCmd := commandPush (p .Build , tag )
1018+ pushCmd .Stdout = os .Stdout
1019+ pushCmd .Stderr = os .Stderr
1020+ trace (pushCmd )
1021+ if err := pushCmd .Run (); err != nil {
1022+ return fmt .Errorf ("failed to push image %s: %w" , fullImageName , err )
1023+ }
1024+
1025+ // Get the digest after push (we only need one)
1026+ if digest == "" {
1027+ d , err := getDigestAfterPush (fullImageName )
1028+ if err == nil {
1029+ digest = d
1030+ } else {
1031+ fmt .Printf ("Warning: Could not get digest for %s: %v\n " , fullImageName , err )
1032+ }
1033+ }
1034+ }
1035+
1036+ // Output the adaptive card
1037+ if p .Builder .Driver == defaultDriver {
1038+ if err := p .writeCard (); err != nil {
1039+ fmt .Printf ("Could not create adaptive card. %s\n " , err )
1040+ }
1041+ }
1042+
1043+ // Write to artifact file
1044+ if p .ArtifactFile != "" && digest != "" {
1045+ if err := drone .WritePluginArtifactFile (
1046+ p .Daemon .RegistryType ,
1047+ p .ArtifactFile ,
1048+ p .Daemon .ArtifactRegistry ,
1049+ p .Build .Repo ,
1050+ digest ,
1051+ p .Build .Tags ,
1052+ ); err != nil {
1053+ fmt .Printf ("Failed to write plugin artifact file at path: %s with error: %s\n " ,
1054+ p .ArtifactFile , err )
1055+ }
1056+ }
1057+
1058+ return nil
1059+ }
0 commit comments