package cmd
import (
"context"
"fmt"
"io"
"os"
"strings"
"time"
"github.com/openeuler/Conch/internal/cli/client"
)
type ImagePushOptions struct {
LocalImage string
RemoteImage string
PlainHTTP bool
Username string
Password string
ConfigPath string
Timeout string
}
func PrintImagePushHelp(out io.Writer) {
fmt.Fprintln(out, "Usage:")
fmt.Fprintln(out, " conch image push [options] <local-image> <remote-image>")
fmt.Fprintln(out, "")
fmt.Fprintln(out, "Description:")
fmt.Fprintln(out, " Push a Conch OCI index from conchd/containerd to a registry.")
fmt.Fprintln(out, "")
fmt.Fprintln(out, "Options:")
fmt.Fprintln(out, " --plain-http")
fmt.Fprintln(out, " allow plain HTTP for the destination registry")
fmt.Fprintln(out, " --username string")
fmt.Fprintln(out, " registry username")
fmt.Fprintln(out, " --password string")
fmt.Fprintln(out, " registry password")
fmt.Fprintln(out, " --timeout duration")
fmt.Fprintln(out, " timeout for this push operation")
fmt.Fprintln(out, " --config string")
fmt.Fprintln(out, " config file path for conchd API discovery")
fmt.Fprintln(out, "")
fmt.Fprintln(out, "Example:")
fmt.Fprintln(out, " conch image push localhost/demo-index:latest hub.oepkgs.net/conch/demo-index:latest")
fmt.Fprintln(out, " conch image push --timeout 30m localhost/demo-index:latest hub.oepkgs.net/conch/demo-index:latest")
fmt.Fprintln(out, " conch image push --plain-http localhost/demo-index:latest conch.example.com/conch/demo-index:latest")
}
func RunImagePush(ctx context.Context, args []string) error {
opts, err := ParseImagePushArgs(args)
if err != nil {
return err
}
var apiTimeout time.Duration
if opts.Timeout != "" {
apiTimeout, err = time.ParseDuration(opts.Timeout)
if err != nil || apiTimeout <= 0 {
return fmt.Errorf("conch image push: invalid --timeout %q", opts.Timeout)
}
}
conchClient, err := client.New(client.Options{ConfigPath: opts.ConfigPath, Timeout: apiTimeout})
if err != nil {
return fmt.Errorf("conch image push: create API client: %w", err)
}
if err := conchClient.PushImage(ctx, client.PushImageRequest{
LocalImage: opts.LocalImage,
RemoteImage: opts.RemoteImage,
PlainHTTP: opts.PlainHTTP,
Username: opts.Username,
Password: opts.Password,
}); err != nil {
return fmt.Errorf("conch image push: %w", err)
}
fmt.Fprintf(os.Stdout, "Pushed image: %s -> %s\n", opts.LocalImage, opts.RemoteImage)
return nil
}
func ParseImagePushArgs(args []string) (ImagePushOptions, error) {
opts := ImagePushOptions{}
images := make([]string, 0, 2)
for i := 0; i < len(args); i++ {
arg := args[i]
switch {
case arg == "--plain-http":
opts.PlainHTTP = true
case arg == "--username":
if i+1 >= len(args) {
return ImagePushOptions{}, fmt.Errorf("conch image push: missing value for %s", arg)
}
opts.Username = args[i+1]
i++
case strings.HasPrefix(arg, "--username="):
opts.Username = strings.TrimPrefix(arg, "--username=")
case arg == "--password":
if i+1 >= len(args) {
return ImagePushOptions{}, fmt.Errorf("conch image push: missing value for %s", arg)
}
opts.Password = args[i+1]
i++
case strings.HasPrefix(arg, "--password="):
opts.Password = strings.TrimPrefix(arg, "--password=")
case arg == "--timeout":
if i+1 >= len(args) {
return ImagePushOptions{}, fmt.Errorf("conch image push: missing value for %s", arg)
}
opts.Timeout = args[i+1]
i++
case strings.HasPrefix(arg, "--timeout="):
opts.Timeout = strings.TrimPrefix(arg, "--timeout=")
case arg == "--config":
if i+1 >= len(args) {
return ImagePushOptions{}, fmt.Errorf("conch image push: missing value for %s", arg)
}
opts.ConfigPath = args[i+1]
i++
case strings.HasPrefix(arg, "--config="):
opts.ConfigPath = strings.TrimPrefix(arg, "--config=")
case strings.HasPrefix(arg, "-"):
return ImagePushOptions{}, fmt.Errorf("conch image push: unknown option %s", arg)
default:
images = append(images, arg)
}
}
if len(images) != 2 {
return ImagePushOptions{}, fmt.Errorf("conch image push: exactly two image names are required: <local-image> <remote-image>")
}
opts.LocalImage = images[0]
opts.RemoteImage = images[1]
return opts, nil
}