github.com/YousefHaggyHeroku/pack@v1.5.5/internal/image/fetcher.go (about)

     1  package image
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"io"
     8  	"strings"
     9  
    10  	"github.com/YousefHaggyHeroku/pack/config"
    11  
    12  	"github.com/buildpacks/imgutil"
    13  	"github.com/buildpacks/imgutil/local"
    14  	"github.com/buildpacks/imgutil/remote"
    15  	"github.com/buildpacks/lifecycle/auth"
    16  	"github.com/docker/docker/api/types"
    17  	"github.com/docker/docker/client"
    18  	"github.com/docker/docker/pkg/jsonmessage"
    19  	"github.com/google/go-containerregistry/pkg/authn"
    20  	"github.com/pkg/errors"
    21  	"golang.org/x/crypto/ssh/terminal"
    22  
    23  	"github.com/YousefHaggyHeroku/pack/internal/style"
    24  	"github.com/YousefHaggyHeroku/pack/logging"
    25  )
    26  
    27  type Fetcher struct {
    28  	docker client.CommonAPIClient
    29  	logger logging.Logger
    30  }
    31  
    32  func NewFetcher(logger logging.Logger, docker client.CommonAPIClient) *Fetcher {
    33  	return &Fetcher{
    34  		logger: logger,
    35  		docker: docker,
    36  	}
    37  }
    38  
    39  var ErrNotFound = errors.New("not found")
    40  
    41  func (f *Fetcher) Fetch(ctx context.Context, name string, daemon bool, pullPolicy config.PullPolicy) (imgutil.Image, error) {
    42  	if daemon {
    43  		if pullPolicy == config.PullNever {
    44  			return f.fetchDaemonImage(name)
    45  		} else if pullPolicy == config.PullIfNotPresent {
    46  			img, err := f.fetchDaemonImage(name)
    47  			if err == nil || !errors.Is(err, ErrNotFound) {
    48  				return img, err
    49  			}
    50  		}
    51  	}
    52  
    53  	image, err := remote.NewImage(name, authn.DefaultKeychain, remote.FromBaseImage(name))
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	remoteFound := image.Found()
    59  
    60  	if daemon {
    61  		if remoteFound {
    62  			f.logger.Debugf("Pulling image %s", style.Symbol(name))
    63  			if err := f.pullImage(ctx, name); err != nil {
    64  				return nil, err
    65  			}
    66  		}
    67  		return f.fetchDaemonImage(name)
    68  	}
    69  
    70  	if !remoteFound {
    71  		return nil, errors.Wrapf(ErrNotFound, "image %s does not exist in registry", style.Symbol(name))
    72  	}
    73  
    74  	return image, nil
    75  }
    76  
    77  func (f *Fetcher) fetchDaemonImage(name string) (imgutil.Image, error) {
    78  	image, err := local.NewImage(name, f.docker, local.FromBaseImage(name))
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	if !image.Found() {
    84  		return nil, errors.Wrapf(ErrNotFound, "image %s does not exist on the daemon", style.Symbol(name))
    85  	}
    86  	return image, nil
    87  }
    88  
    89  func (f *Fetcher) pullImage(ctx context.Context, imageID string) error {
    90  	regAuth, err := registryAuth(imageID)
    91  	if err != nil {
    92  		return err
    93  	}
    94  	rc, err := f.docker.ImagePull(ctx, imageID, types.ImagePullOptions{
    95  		RegistryAuth: regAuth,
    96  	})
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	writer := logging.GetWriterForLevel(f.logger, logging.InfoLevel)
   102  	termFd, isTerm := isTerminal(writer)
   103  
   104  	err = jsonmessage.DisplayJSONMessagesStream(rc, &colorizedWriter{writer}, termFd, isTerm, nil)
   105  	if err != nil {
   106  		return err
   107  	}
   108  
   109  	return rc.Close()
   110  }
   111  
   112  func isTerminal(w io.Writer) (uintptr, bool) {
   113  	type descriptor interface {
   114  		Fd() uintptr
   115  	}
   116  
   117  	if f, ok := w.(descriptor); ok {
   118  		termFd := f.Fd()
   119  		isTerm := terminal.IsTerminal(int(termFd))
   120  		return termFd, isTerm
   121  	}
   122  
   123  	return 0, false
   124  }
   125  
   126  func registryAuth(ref string) (string, error) {
   127  	_, a, err := auth.ReferenceForRepoName(authn.DefaultKeychain, ref)
   128  	if err != nil {
   129  		return "", errors.Wrapf(err, "resolve auth for ref %s", ref)
   130  	}
   131  	authConfig, err := a.Authorization()
   132  	if err != nil {
   133  		return "", err
   134  	}
   135  
   136  	dataJSON, err := json.Marshal(authConfig)
   137  	if err != nil {
   138  		return "", err
   139  	}
   140  
   141  	return base64.StdEncoding.EncodeToString(dataJSON), nil
   142  }
   143  
   144  type colorizedWriter struct {
   145  	writer io.Writer
   146  }
   147  
   148  type colorFunc = func(string, ...interface{}) string
   149  
   150  func (w *colorizedWriter) Write(p []byte) (n int, err error) {
   151  	msg := string(p)
   152  	colorizers := map[string]colorFunc{
   153  		"Waiting":           style.Waiting,
   154  		"Pulling fs layer":  style.Waiting,
   155  		"Downloading":       style.Working,
   156  		"Download complete": style.Working,
   157  		"Extracting":        style.Working,
   158  		"Pull complete":     style.Complete,
   159  		"Already exists":    style.Complete,
   160  		"=":                 style.ProgressBar,
   161  		">":                 style.ProgressBar,
   162  	}
   163  	for pattern, colorize := range colorizers {
   164  		msg = strings.Replace(msg, pattern, colorize(pattern), -1)
   165  	}
   166  	return w.writer.Write([]byte(msg))
   167  }