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  }