-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add saucectl docker push cmd (#860)
* feat: add saucectl docker push cmd * refine * refine * make it work: login also requires repo name * mv region to login-region to reduce confusion * rename * Update internal/cmd/docker/cmd.go Co-authored-by: Alex Plischke <[email protected]> * Update internal/cmd/docker/push.go Co-authored-by: Alex Plischke <[email protected]> * Update internal/cmd/docker/cmd.go Co-authored-by: Mike Han <[email protected]> * update according to comments * Update internal/cmd/docker/push.go Co-authored-by: Alex Plischke <[email protected]> * Update internal/http/docker.go Co-authored-by: Alex Plischke <[email protected]> * move docker client into imagerunner * use regex to verify image name and extract repo name * give more time * 🔪 * Update internal/cmd/docker/push.go Co-authored-by: Alex Plischke <[email protected]> * still use repo args and unify commands prompt * add repo name check back * remove default value in description * extract repo name * 🔪 * update err msg --------- Co-authored-by: Alex Plischke <[email protected]> Co-authored-by: Mike Han <[email protected]>
- Loading branch information
1 parent
b0864c3
commit b5c3b6f
Showing
6 changed files
with
244 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package docker | ||
|
||
import ( | ||
"errors" | ||
"time" | ||
|
||
"github.com/saucelabs/saucectl/internal/http" | ||
"github.com/saucelabs/saucectl/internal/region" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var ( | ||
imageRunnerService http.ImageRunner | ||
imageRunnerServiceTimeout = 1 * time.Minute | ||
) | ||
|
||
func Command(preRun func(cmd *cobra.Command, args []string)) *cobra.Command { | ||
var regio string | ||
|
||
cmd := &cobra.Command{ | ||
Use: "docker", | ||
Short: "Interact with Sauce Container Registry", | ||
SilenceUsage: true, | ||
TraverseChildren: true, | ||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error { | ||
if preRun != nil { | ||
preRun(cmd, args) | ||
} | ||
|
||
reg := region.FromString(regio) | ||
if reg == region.None { | ||
return errors.New("invalid region: must be one of [us-west-1, eu-central-1]") | ||
} | ||
|
||
creds := reg.Credentials() | ||
url := reg.APIBaseURL() | ||
imageRunnerService = http.NewImageRunner(url, creds, imageRunnerServiceTimeout) | ||
|
||
return nil | ||
}, | ||
} | ||
|
||
flags := cmd.PersistentFlags() | ||
flags.StringVarP(®io, "region", "r", "us-west-1", "The Sauce Labs region to login. Options: us-west-1, eu-central-1.") | ||
|
||
cmd.AddCommand( | ||
PushCommand(), | ||
) | ||
|
||
return cmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package docker | ||
|
||
import ( | ||
"context" | ||
"encoding/base64" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"os" | ||
"strings" | ||
"time" | ||
|
||
"github.com/docker/docker/api/types" | ||
"github.com/docker/docker/api/types/registry" | ||
"github.com/docker/docker/client" | ||
cmds "github.com/saucelabs/saucectl/internal/cmd" | ||
"github.com/saucelabs/saucectl/internal/segment" | ||
"github.com/saucelabs/saucectl/internal/usage" | ||
"github.com/spf13/cobra" | ||
"golang.org/x/text/cases" | ||
"golang.org/x/text/language" | ||
) | ||
|
||
func PushCommand() *cobra.Command { | ||
var timeout time.Duration | ||
var quiet bool | ||
|
||
cmd := &cobra.Command{ | ||
Use: "push <image_name>", | ||
Short: "Push a Docker image to the Sauce Labs Container Registry.", | ||
SilenceUsage: true, | ||
Args: func(cmd *cobra.Command, args []string) error { | ||
if len(args) == 0 || args[0] == "" { | ||
return errors.New("no docker image specified") | ||
} | ||
|
||
return nil | ||
}, | ||
PreRun: func(cmd *cobra.Command, args []string) { | ||
tracker := segment.DefaultTracker | ||
|
||
go func() { | ||
tracker.Collect( | ||
cases.Title(language.English).String(cmds.FullName(cmd)), | ||
usage.Properties{}.SetFlags(cmd.Flags()), | ||
) | ||
_ = tracker.Close() | ||
}() | ||
}, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
image := args[0] | ||
repo, err := extractRepo(image) | ||
if err != nil { | ||
return err | ||
} | ||
auth, err := imageRunnerService.RegistryLogin(context.Background(), repo) | ||
if err != nil { | ||
return fmt.Errorf("failed to fetch auth token: %v", err) | ||
} | ||
return pushDockerImage(image, auth.Username, auth.Password, timeout, quiet) | ||
}, | ||
} | ||
|
||
flags := cmd.PersistentFlags() | ||
flags.DurationVar(&timeout, "timeout", 5*time.Minute, "Configure the timeout duration for docker push.") | ||
flags.BoolVar(&quiet, "quiet", false, "Run silently, suppressing output messages.") | ||
|
||
return cmd | ||
} | ||
|
||
func pushDockerImage(imageName, username, password string, timeout time.Duration, quiet bool) error { | ||
ctx, cancel := context.WithTimeout(context.Background(), timeout) | ||
defer cancel() | ||
|
||
cli, err := client.NewClientWithOpts(client.FromEnv) | ||
if err != nil { | ||
return fmt.Errorf("failed to create docker client: %v", err) | ||
} | ||
|
||
authConfig := registry.AuthConfig{ | ||
Username: username, | ||
Password: password, | ||
} | ||
|
||
authBytes, err := json.Marshal(authConfig) | ||
if err != nil { | ||
return fmt.Errorf("failed to marshal docker auth: %v", err) | ||
} | ||
authBase64 := base64.URLEncoding.EncodeToString(authBytes) | ||
|
||
// Push the image to the registry | ||
pushOptions := types.ImagePushOptions{RegistryAuth: authBase64} | ||
out, err := cli.ImagePush(ctx, imageName, pushOptions) | ||
if err != nil { | ||
return fmt.Errorf("failed to push image: %v", err) | ||
} | ||
defer out.Close() | ||
|
||
if quiet { | ||
return nil | ||
} | ||
|
||
// Print the push output | ||
_, err = io.Copy(os.Stdout, out) | ||
if err != nil { | ||
return fmt.Errorf("docker output: %v", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func extractRepo(input string) (string, error) { | ||
// Example: us-west4-docker.pkg.dev/sauce-hto-p-jy6b/sauce-devx-team-sauce/ubuntu:experiment | ||
items := strings.Split(input, "/") | ||
if len(items) >= 3 { | ||
return items[2], nil | ||
} | ||
return "", fmt.Errorf("unable to extract repo name from docker image") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters