github.com/Benchkram/bob@v0.0.0-20220321080157-7c8f3876e225/pkg/dockermobyutil/registry.go (about) 1 package dockermobyutil 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "math/rand" 9 "os" 10 "path/filepath" 11 "strings" 12 "time" 13 14 "github.com/Benchkram/errz" 15 "github.com/docker/docker/api/types" 16 "github.com/docker/docker/client" 17 ) 18 19 var ( 20 ErrImageNotFound = fmt.Errorf("image not found") 21 ) 22 23 type RegistryClient interface { 24 ImageExists(image string) (bool, error) 25 ImageHash(image string) (string, error) 26 ImageSave(image string) (pathToArchive string, _ error) 27 ImageRemove(image string) error 28 ImageTag(src string, target string) error 29 30 ImageLoad(pathToArchive string) error 31 } 32 33 type R struct { 34 client *client.Client 35 archiveDir string 36 } 37 38 func NewRegistryClient() RegistryClient { 39 cli, err := client.NewClientWithOpts(client.FromEnv) 40 if err != nil { 41 errz.Fatal(err) 42 } 43 44 r := &R{ 45 client: cli, 46 archiveDir: os.TempDir(), 47 } 48 49 return r 50 } 51 52 func (r *R) ImageExists(image string) (bool, error) { 53 _, err := r.ImageHash(image) 54 if err != nil { 55 if errors.Is(err, ErrImageNotFound) { 56 return false, nil 57 } 58 return false, err 59 } 60 61 return true, nil 62 } 63 64 func (r *R) ImageHash(image string) (string, error) { 65 summaries, err := r.client.ImageList(context.Background(), types.ImageListOptions{All: false}) 66 if err != nil { 67 return "", err 68 } 69 70 var selected types.ImageSummary 71 for _, s := range summaries { 72 for _, rtag := range s.RepoTags { 73 if rtag == image { 74 selected = s 75 break 76 } 77 } 78 } 79 80 if selected.ID == "" { 81 return "", fmt.Errorf("%s, %w", image, ErrImageNotFound) 82 } 83 84 return selected.ID, nil 85 } 86 87 func (r *R) imageSaveToPath(image string, savedir string) (pathToArchive string, _ error) { 88 reader, err := r.client.ImageSave(context.Background(), []string{image}) 89 if err != nil { 90 return "", err 91 } 92 defer reader.Close() 93 94 body, err := ioutil.ReadAll(reader) 95 if err != nil { 96 return "", err 97 } 98 99 // rndExtension is added to the archive name. It prevents overwrite of images in tmp directory in case 100 // of a image beeing used as target in multiple tasks (which should be avoided). 101 rndExtension := randStringRunes(8) 102 103 image = strings.ReplaceAll(image, "/", "-") 104 105 pathToArchive = filepath.Join(savedir, image+"-"+rndExtension+".tar") 106 err = ioutil.WriteFile(pathToArchive, body, 0644) 107 if err != nil { 108 return "", err 109 } 110 111 return pathToArchive, nil 112 } 113 114 // ImageSave wraps for `docker save` with the addition to add a random string 115 // to archive name. 116 func (r *R) ImageSave(image string) (pathToArchive string, _ error) { 117 return r.imageSaveToPath(image, r.archiveDir) 118 } 119 120 // ImageRemove from registry 121 func (r *R) ImageRemove(imageID string) error { 122 options := types.ImageRemoveOptions{ 123 Force: true, 124 PruneChildren: true, 125 } 126 _, err := r.client.ImageRemove(context.Background(), imageID, options) 127 if err != nil { 128 return err 129 } 130 131 return nil 132 } 133 134 // ImageLoad from tar archive 135 func (r *R) ImageLoad(imgpath string) error { 136 f, err := os.Open(imgpath) 137 if err != nil { 138 return err 139 } 140 defer f.Close() 141 142 resp, err := r.client.ImageLoad(context.Background(), f, false) 143 if err != nil { 144 return err 145 } 146 147 return resp.Body.Close() 148 } 149 150 // ImageLoad from tar archive 151 func (r *R) ImageTag(src string, target string) error { 152 return r.client.ImageTag(context.Background(), src, target) 153 } 154 155 // https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-go 156 var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz") 157 158 func randStringRunes(n int) string { 159 rand.Seed(time.Now().UnixNano()) 160 161 b := make([]rune, n) 162 for i := range b { 163 b[i] = letterRunes[rand.Intn(len(letterRunes))] 164 } 165 return string(b) 166 }