diff --git a/cmd/ctr/commands/resolver.go b/cmd/ctr/commands/resolver.go new file mode 100644 index 000000000..a8cb77281 --- /dev/null +++ b/cmd/ctr/commands/resolver.go @@ -0,0 +1,92 @@ +package commands + +import ( + "bufio" + gocontext "context" + "crypto/tls" + "fmt" + "net" + "net/http" + "strings" + "time" + + "github.com/containerd/console" + "github.com/containerd/containerd/remotes" + "github.com/containerd/containerd/remotes/docker" + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +// PushTracker returns a new InMemoryTracker which tracks the ref status +var PushTracker = docker.NewInMemoryTracker() + +func passwordPrompt() (string, error) { + c := console.Current() + defer c.Reset() + + if err := c.DisableEcho(); err != nil { + return "", errors.Wrap(err, "failed to disable echo") + } + + line, _, err := bufio.NewReader(c).ReadLine() + if err != nil { + return "", errors.Wrap(err, "failed to read line") + } + return string(line), nil +} + +// GetResolver prepares the resolver from the environment and options +func GetResolver(ctx gocontext.Context, clicontext *cli.Context) (remotes.Resolver, error) { + username := clicontext.String("user") + var secret string + if i := strings.IndexByte(username, ':'); i > 0 { + secret = username[i+1:] + username = username[0:i] + } + options := docker.ResolverOptions{ + PlainHTTP: clicontext.Bool("plain-http"), + Tracker: PushTracker, + } + if username != "" { + if secret == "" { + fmt.Printf("Password: ") + + var err error + secret, err = passwordPrompt() + if err != nil { + return nil, err + } + + fmt.Print("\n") + } + } else if rt := clicontext.String("refresh"); rt != "" { + secret = rt + } + + options.Credentials = func(host string) (string, string, error) { + // Only one host + return username, secret, nil + } + + tr := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, + }).DialContext, + MaxIdleConns: 10, + IdleConnTimeout: 30 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: clicontext.Bool("insecure"), + }, + ExpectContinueTimeout: 5 * time.Second, + } + + options.Client = &http.Client{ + Transport: tr, + } + + return docker.NewResolver(options), nil +} diff --git a/cmd/ctr/fetch.go b/cmd/ctr/fetch.go index a06afa271..8966ff4f2 100644 --- a/cmd/ctr/fetch.go +++ b/cmd/ctr/fetch.go @@ -57,7 +57,7 @@ func fetch(ref string, cliContext *cli.Context) (containerd.Image, error) { } defer cancel() - resolver, err := getResolver(ctx, cliContext) + resolver, err := commands.GetResolver(ctx, cliContext) if err != nil { return nil, err } diff --git a/cmd/ctr/fetchobject.go b/cmd/ctr/fetchobject.go index 15cf346d9..8d6f9601e 100644 --- a/cmd/ctr/fetchobject.go +++ b/cmd/ctr/fetchobject.go @@ -26,7 +26,7 @@ var fetchObjectCommand = cli.Command{ ctx, cancel := commands.AppContext(context) defer cancel() - resolver, err := getResolver(ctx, context) + resolver, err := commands.GetResolver(ctx, context) if err != nil { return err } diff --git a/cmd/ctr/push.go b/cmd/ctr/push.go index c0c8e4420..90a5d8d39 100644 --- a/cmd/ctr/push.go +++ b/cmd/ctr/push.go @@ -21,10 +21,6 @@ import ( "golang.org/x/sync/errgroup" ) -var ( - pushTracker = docker.NewInMemoryTracker() -) - var pushCommand = cli.Command{ Name: "push", Usage: "push an image to a remote", @@ -74,11 +70,11 @@ var pushCommand = cli.Command{ desc = img.Target } - resolver, err := getResolver(ctx, context) + resolver, err := commands.GetResolver(ctx, context) if err != nil { return err } - ongoing := newPushJobs(pushTracker) + ongoing := newPushJobs(commands.PushTracker) eg, ctx := errgroup.WithContext(ctx) diff --git a/cmd/ctr/pushobject.go b/cmd/ctr/pushobject.go index 57e804825..ac3023f88 100644 --- a/cmd/ctr/pushobject.go +++ b/cmd/ctr/pushobject.go @@ -33,7 +33,7 @@ var pushObjectCommand = cli.Command{ } defer cancel() - resolver, err := getResolver(ctx, context) + resolver, err := commands.GetResolver(ctx, context) if err != nil { return err } diff --git a/cmd/ctr/utils.go b/cmd/ctr/utils.go index eae6a908e..ad00b40ef 100644 --- a/cmd/ctr/utils.go +++ b/cmd/ctr/utils.go @@ -1,95 +1,13 @@ package main import ( - "bufio" - gocontext "context" - "crypto/tls" "encoding/csv" "fmt" - "net" - "net/http" "strings" - "time" - "github.com/containerd/console" - "github.com/containerd/containerd/remotes" - "github.com/containerd/containerd/remotes/docker" specs "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" - "github.com/urfave/cli" ) -func passwordPrompt() (string, error) { - c := console.Current() - defer c.Reset() - - if err := c.DisableEcho(); err != nil { - return "", errors.Wrap(err, "failed to disable echo") - } - - line, _, err := bufio.NewReader(c).ReadLine() - if err != nil { - return "", errors.Wrap(err, "failed to read line") - } - return string(line), nil -} - -// getResolver prepares the resolver from the environment and options. -func getResolver(ctx gocontext.Context, clicontext *cli.Context) (remotes.Resolver, error) { - username := clicontext.String("user") - var secret string - if i := strings.IndexByte(username, ':'); i > 0 { - secret = username[i+1:] - username = username[0:i] - } - options := docker.ResolverOptions{ - PlainHTTP: clicontext.Bool("plain-http"), - Tracker: pushTracker, - } - if username != "" { - if secret == "" { - fmt.Printf("Password: ") - - var err error - secret, err = passwordPrompt() - if err != nil { - return nil, err - } - - fmt.Print("\n") - } - } else if rt := clicontext.String("refresh"); rt != "" { - secret = rt - } - - options.Credentials = func(host string) (string, string, error) { - // Only one host - return username, secret, nil - } - - tr := &http.Transport{ - Proxy: http.ProxyFromEnvironment, - DialContext: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - DualStack: true, - }).DialContext, - MaxIdleConns: 10, - IdleConnTimeout: 30 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: clicontext.Bool("insecure"), - }, - ExpectContinueTimeout: 5 * time.Second, - } - - options.Client = &http.Client{ - Transport: tr, - } - - return docker.NewResolver(options), nil -} - // parseMountFlag parses a mount string in the form "type=foo,source=/path,destination=/target,options=rbind:rw" func parseMountFlag(m string) (specs.Mount, error) { mount := specs.Mount{}