Updates the docker resolver to support authenticating with registries. Signed-off-by: Derek McGowan <derek@mcgstyle.net>
		
			
				
	
	
		
			147 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			147 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package main
 | 
						|
 | 
						|
import (
 | 
						|
	"bufio"
 | 
						|
	"context"
 | 
						|
	"crypto/tls"
 | 
						|
	"fmt"
 | 
						|
	"net"
 | 
						|
	"net/http"
 | 
						|
	"path/filepath"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	imagesapi "github.com/containerd/containerd/api/services/images"
 | 
						|
	"github.com/containerd/containerd/content"
 | 
						|
	"github.com/containerd/containerd/images"
 | 
						|
	"github.com/containerd/containerd/remotes"
 | 
						|
	"github.com/containerd/containerd/remotes/docker"
 | 
						|
	imagesservice "github.com/containerd/containerd/services/images"
 | 
						|
	"github.com/crosbymichael/console"
 | 
						|
	"github.com/pkg/errors"
 | 
						|
	"github.com/urfave/cli"
 | 
						|
	"google.golang.org/grpc"
 | 
						|
)
 | 
						|
 | 
						|
var registryFlags = []cli.Flag{
 | 
						|
	cli.BoolFlag{
 | 
						|
		Name:  "skip-verify,k",
 | 
						|
		Usage: "Skip SSL certificate validation",
 | 
						|
	},
 | 
						|
	cli.BoolFlag{
 | 
						|
		Name:  "plain-http",
 | 
						|
		Usage: "Allow connections using plain HTTP",
 | 
						|
	},
 | 
						|
	cli.StringFlag{
 | 
						|
		Name:  "user,u",
 | 
						|
		Usage: "user[:password] Registry user and password",
 | 
						|
	},
 | 
						|
	cli.StringFlag{
 | 
						|
		Name:  "refresh",
 | 
						|
		Usage: "Refresh token for authorization server",
 | 
						|
	},
 | 
						|
}
 | 
						|
 | 
						|
func resolveContentStore(context *cli.Context) (*content.Store, error) {
 | 
						|
	root := filepath.Join(context.GlobalString("root"), "content")
 | 
						|
	if !filepath.IsAbs(root) {
 | 
						|
		var err error
 | 
						|
		root, err = filepath.Abs(root)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return content.NewStore(root)
 | 
						|
}
 | 
						|
 | 
						|
func resolveImageStore(clicontext *cli.Context) (images.Store, error) {
 | 
						|
	conn, err := connectGRPC(clicontext)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return imagesservice.NewStoreFromClient(imagesapi.NewImagesClient(conn)), nil
 | 
						|
}
 | 
						|
 | 
						|
func connectGRPC(context *cli.Context) (*grpc.ClientConn, error) {
 | 
						|
	address := context.GlobalString("address")
 | 
						|
	timeout := context.GlobalDuration("connect-timeout")
 | 
						|
	return grpc.Dial(address,
 | 
						|
		grpc.WithTimeout(timeout),
 | 
						|
		grpc.WithBlock(),
 | 
						|
		grpc.WithInsecure(),
 | 
						|
		grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
 | 
						|
			return net.DialTimeout("unix", address, timeout)
 | 
						|
		}),
 | 
						|
	)
 | 
						|
}
 | 
						|
 | 
						|
// getResolver prepares the resolver from the environment and options.
 | 
						|
func getResolver(ctx context.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"),
 | 
						|
	}
 | 
						|
	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
 | 
						|
}
 | 
						|
 | 
						|
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
 | 
						|
}
 |