github.com/saucelabs/saucectl@v0.175.1/internal/cmd/docker/push.go (about)

     1  package docker
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"time"
    12  
    13  	"github.com/docker/docker/api/types"
    14  	"github.com/docker/docker/api/types/registry"
    15  	"github.com/docker/docker/client"
    16  	cmds "github.com/saucelabs/saucectl/internal/cmd"
    17  	"github.com/saucelabs/saucectl/internal/segment"
    18  	"github.com/saucelabs/saucectl/internal/usage"
    19  	"github.com/spf13/cobra"
    20  	"golang.org/x/text/cases"
    21  	"golang.org/x/text/language"
    22  )
    23  
    24  func PushCommand() *cobra.Command {
    25  	var timeout time.Duration
    26  	var quiet bool
    27  
    28  	cmd := &cobra.Command{
    29  		Use:          "push <image_name>",
    30  		Short:        "Push a Docker image to the Sauce Labs Container Registry.",
    31  		SilenceUsage: true,
    32  		Args: func(cmd *cobra.Command, args []string) error {
    33  			if len(args) == 0 || args[0] == "" {
    34  				return errors.New("no docker image specified")
    35  			}
    36  
    37  			return nil
    38  		},
    39  		PreRun: func(cmd *cobra.Command, args []string) {
    40  			tracker := segment.DefaultTracker
    41  
    42  			go func() {
    43  				tracker.Collect(
    44  					cases.Title(language.English).String(cmds.FullName(cmd)),
    45  					usage.Properties{}.SetFlags(cmd.Flags()),
    46  				)
    47  				_ = tracker.Close()
    48  			}()
    49  		},
    50  		RunE: func(cmd *cobra.Command, args []string) error {
    51  			image := args[0]
    52  			auth, err := imageRunnerService.RegistryLogin(context.Background(), image)
    53  			if err != nil {
    54  				return fmt.Errorf("failed to fetch auth token: %v", err)
    55  			}
    56  			return pushDockerImage(image, auth.Username, auth.Password, timeout, quiet)
    57  		},
    58  	}
    59  
    60  	flags := cmd.PersistentFlags()
    61  	flags.DurationVar(&timeout, "timeout", 5*time.Minute, "Configure the timeout duration for docker push.")
    62  	flags.BoolVar(&quiet, "quiet", false, "Run silently, suppressing output messages.")
    63  
    64  	return cmd
    65  }
    66  
    67  func pushDockerImage(imageName, username, password string, timeout time.Duration, quiet bool) error {
    68  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
    69  	defer cancel()
    70  
    71  	cli, err := client.NewClientWithOpts(client.FromEnv)
    72  	if err != nil {
    73  		return fmt.Errorf("failed to create docker client: %v", err)
    74  	}
    75  
    76  	authConfig := registry.AuthConfig{
    77  		Username: username,
    78  		Password: password,
    79  	}
    80  
    81  	authBytes, err := json.Marshal(authConfig)
    82  	if err != nil {
    83  		return fmt.Errorf("failed to marshal docker auth: %v", err)
    84  	}
    85  	authBase64 := base64.URLEncoding.EncodeToString(authBytes)
    86  
    87  	// Push the image to the registry
    88  	pushOptions := types.ImagePushOptions{RegistryAuth: authBase64}
    89  	out, err := cli.ImagePush(ctx, imageName, pushOptions)
    90  	if err != nil {
    91  		return fmt.Errorf("failed to push image: %v", err)
    92  	}
    93  	defer out.Close()
    94  
    95  	if quiet {
    96  		return nil
    97  	}
    98  
    99  	// Print the push output
   100  	_, err = io.Copy(os.Stdout, out)
   101  	if err != nil {
   102  		return fmt.Errorf("docker output: %v", err)
   103  	}
   104  
   105  	return nil
   106  }