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 }