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 }