github.com/quay/claircore@v1.5.28/updater/offline.go (about)

     1  package updater
     2  
     3  import (
     4  	"archive/zip"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"io/fs"
    10  	"net/url"
    11  
    12  	"github.com/klauspost/compress/zstd"
    13  )
    14  
    15  // Fetch runs only the Fetch step of the update process, writing it out to
    16  // "out".
    17  //
    18  // If "prev" is populated with the output of a previous run of this method, only
    19  // changes since what was recorded in "prev" are written out.
    20  func (u *Updater) Fetch(ctx context.Context, prev io.ReaderAt, out io.Writer) error {
    21  	z := zip.NewWriter(out)
    22  	var prevFS fs.FS
    23  	if prev != nil {
    24  		pz, h, err := openZip(prev)
    25  		if err != nil {
    26  			return err
    27  		}
    28  		if h == exportV1 {
    29  			prevFS = pz
    30  		}
    31  	}
    32  	if err := u.exportV1(ctx, z, prevFS); err != nil {
    33  		return err
    34  	}
    35  	v := make(url.Values)
    36  	v.Set(exportHeader, exportV1)
    37  	if err := z.SetComment(v.Encode()); err != nil {
    38  		return err
    39  	}
    40  	if err := z.Close(); err != nil {
    41  		return err
    42  	}
    43  	if f, ok := out.(syncer); ok {
    44  		if err := f.Sync(); err != nil {
    45  			return err
    46  		}
    47  	}
    48  	return nil
    49  }
    50  
    51  type syncer interface{ Sync() error }
    52  
    53  // Parse runs the "second half" of the update process, using the contents of
    54  // "in," which must have been populated by a previous call to Fetch.
    55  //
    56  // The reader at "in" must have some way to detect its size.
    57  func (u *Updater) Parse(ctx context.Context, in io.ReaderAt) error {
    58  	z, h, err := openZip(in)
    59  	if err != nil {
    60  		return err
    61  	}
    62  	switch h {
    63  	case exportV1:
    64  		return u.importV1(ctx, z)
    65  	case "":
    66  		return errors.New("updater: file not produced by claircore")
    67  	default:
    68  	}
    69  	return fmt.Errorf("updater: unrecognized export version %q", h)
    70  }
    71  
    72  // OpenZip opens the zip pointed to by f if a size can be determined, and also
    73  // returns the magic comment, if present.
    74  func openZip(in io.ReaderAt) (*zip.Reader, string, error) {
    75  	var sz int64
    76  	switch v := in.(type) {
    77  	case sizer:
    78  		sz = v.Size()
    79  	case fileStat:
    80  		fi, err := v.Stat()
    81  		if err != nil {
    82  			return nil, "", err
    83  		}
    84  		sz = fi.Size()
    85  	case io.Seeker:
    86  		cur, err := v.Seek(0, io.SeekCurrent)
    87  		if err != nil {
    88  			return nil, "", err
    89  		}
    90  		sz, err = v.Seek(0, io.SeekEnd)
    91  		if err != nil {
    92  			return nil, "", err
    93  		}
    94  		if _, err := v.Seek(cur, io.SeekStart); err != nil {
    95  			return nil, "", err
    96  		}
    97  	default:
    98  		return nil, "", errors.New("updater: unable to determine size of zip file")
    99  	}
   100  	z, err := zip.NewReader(in, sz)
   101  	if err != nil {
   102  		return nil, "", err
   103  	}
   104  	v, _ := url.ParseQuery(z.Comment)
   105  	return z, v.Get(exportHeader), nil
   106  }
   107  
   108  type (
   109  	fileStat interface{ Stat() (fs.FileInfo, error) }
   110  	sizer    interface{ Size() int64 }
   111  )
   112  
   113  const (
   114  	exportHeader    = `ClaircoreUpdaterExport`
   115  	zstdCompression = 93 // zstd, according to PKWARE spec
   116  )
   117  
   118  func init() {
   119  	zip.RegisterCompressor(zstdCompression, newZstdCompressor)
   120  	zip.RegisterDecompressor(zstdCompression, newZstdDecompressor)
   121  }
   122  
   123  func newZstdCompressor(w io.Writer) (io.WriteCloser, error) {
   124  	c, err := zstd.NewWriter(w)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	return c, nil
   129  }
   130  
   131  func newZstdDecompressor(r io.Reader) io.ReadCloser {
   132  	c, err := zstd.NewReader(r)
   133  	if err != nil {
   134  		panic(err)
   135  	}
   136  	return &cmpWrapper{c}
   137  }
   138  
   139  type cmpWrapper struct {
   140  	*zstd.Decoder
   141  }
   142  
   143  func (w *cmpWrapper) Close() error {
   144  	w.Decoder.Close()
   145  	return nil
   146  }