github.com/quay/claircore@v1.5.28/updater/offline_v1.go (about) 1 package updater 2 3 import ( 4 "archive/zip" 5 "context" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "io/fs" 11 "os" 12 "path" 13 "runtime" 14 "sync" 15 "time" 16 17 "github.com/google/uuid" 18 "github.com/quay/zlog" 19 "golang.org/x/sync/errgroup" 20 21 "github.com/quay/claircore/updater/driver/v1" 22 ) 23 24 /* 25 This file implements the V1 import/export format, aka zip-of-zips. 26 27 The config is written to the root at "config.json", which should allow the Parse 28 step to ensure it has the same configuration parameters that the Fetch step did. 29 30 All Updaters created at the Fetch step have their data recorded in 31 subdirectories keyed by name. 32 */ 33 34 const exportV1 = `1` 35 36 func (u *Updater) importV1(ctx context.Context, sys fs.FS) error { 37 var cfg driver.Configs 38 f, err := sys.Open("config.json") 39 if err != nil { 40 return fmt.Errorf("updater import: %w", err) 41 } 42 defer func() { 43 if err := f.Close(); err != nil { 44 zlog.Warn(ctx).Err(err).Msg("error closing config.json") 45 } 46 }() 47 if err := json.NewDecoder(f).Decode(&cfg); err != nil { 48 return err 49 } 50 51 us, err := u.updaters(ctx, cfg) 52 if err != nil { 53 return err 54 } 55 zlog.Info(ctx). 56 Int("ct", len(us)). 57 Msg("got updaters") 58 59 spool, err := os.CreateTemp(tmpDir, tmpPattern) 60 if err != nil { 61 return err 62 } 63 defer func() { 64 spoolname := spool.Name() 65 if err := os.Remove(spoolname); err != nil { 66 zlog.Warn(ctx).Str("filename", spoolname).Err(err).Msg("unable to remove spool file") 67 } 68 if err := spool.Close(); err != nil { 69 zlog.Warn(ctx).Str("filename", spoolname).Err(err).Msg("error closing spool file") 70 } 71 }() 72 73 for _, upd := range us { 74 name := upd.Name 75 ctx := zlog.ContextWithValues(ctx, "updater", name) 76 fi, err := fs.Stat(sys, name) 77 switch { 78 case errors.Is(err, nil): 79 case errors.Is(err, fs.ErrNotExist): 80 zlog.Info(ctx). 81 Msg("no import, skipping") 82 continue 83 default: 84 return err 85 } 86 if !fi.IsDir() { 87 return errors.New("malformed input") 88 } 89 if _, err := spool.Seek(0, io.SeekStart); err != nil { 90 return err 91 } 92 f, err := sys.Open(path.Join(name, `data`)) 93 if err != nil { 94 return err 95 } 96 sz, err := io.Copy(spool, f) 97 f.Close() 98 if err != nil { 99 return err 100 } 101 z, err := zip.NewReader(spool, sz) 102 if err != nil { 103 return err 104 } 105 106 res, err := u.parseOne(ctx, upd, z) 107 if err != nil { 108 return err 109 } 110 111 b, err := fs.ReadFile(sys, path.Join(name, `fingerprint`)) 112 if err != nil { 113 return err 114 } 115 fp := driver.Fingerprint(b) 116 117 var ref uuid.UUID 118 b, err = fs.ReadFile(sys, path.Join(name, `ref`)) 119 if err != nil { 120 return err 121 } 122 if err := ref.UnmarshalText(b); err != nil { 123 return err 124 } 125 126 // Load into DB. 127 if len(res.Vulnerabilities.Vulnerability) != 0 { 128 if err := u.store.UpdateVulnerabilities(ctx, ref, name, fp, res.Vulnerabilities); err != nil { 129 return err 130 } 131 zlog.Info(ctx).Stringer("ref", ref).Msg("updated vulnerabilites") 132 } 133 if len(res.Enrichments) != 0 { 134 if err := u.store.UpdateEnrichments(ctx, ref, name, fp, res.Enrichments); err != nil { 135 return err 136 } 137 zlog.Info(ctx).Stringer("ref", ref).Msg("updated enrichments") 138 } 139 } 140 141 return nil 142 } 143 144 func (u *Updater) exportV1(ctx context.Context, z *zip.Writer, prev fs.FS) error { 145 now := time.Now() 146 147 w, err := z.CreateHeader(&zip.FileHeader{ 148 Name: "config.json", 149 Comment: "updater configuration from the producer", 150 Modified: now, 151 Method: zstdCompression, 152 }) 153 if err != nil { 154 return err 155 } 156 if err := json.NewEncoder(w).Encode(u.configs); err != nil { 157 return err 158 } 159 160 us, err := u.updaters(ctx, u.configs) 161 if err != nil { 162 return err 163 } 164 zlog.Info(ctx). 165 Int("ct", len(us)). 166 Msg("got updaters") 167 168 // WaitGroup for the worker goroutines. 169 var wg sync.WaitGroup 170 lim := runtime.GOMAXPROCS(0) 171 wg.Add(lim) 172 pfps := make(map[string]driver.Fingerprint) 173 if prev != nil { 174 walk := func(p string, _ fs.DirEntry, err error) error { 175 if err != nil { 176 return err 177 } 178 dir, base := path.Split(p) 179 if dir == "." || base != "fingerprint" { 180 // Don't bother with anything that's not the fingerprint file. 181 return nil 182 } 183 184 b, err := fs.ReadFile(prev, p) 185 if err != nil { 186 return err 187 } 188 pfps[dir] = driver.Fingerprint(b) 189 return nil 190 } 191 if err := fs.WalkDir(prev, ".", walk); err != nil { 192 return err 193 } 194 } 195 // ErrGroup for the workers, feeder, and closer goroutines. 196 eg, ctx := errgroup.WithContext(ctx) 197 feed, res := make(chan taggedUpdater), make(chan *result) 198 199 eg.Go(feeder(ctx, feed, us)) 200 // Closer goroutine. 201 eg.Go(func() error { 202 wg.Wait() 203 close(res) 204 return nil 205 }) 206 eg.Go(func() error { 207 // Collect results and write them out to the zip at "z". 208 for r := range res { 209 if err := addUpdater(ctx, z, now, r); err != nil { 210 return err 211 } 212 } 213 return nil 214 }) 215 for i := 0; i < lim; i++ { 216 // Worker goroutine. 217 eg.Go(func() error { 218 defer wg.Done() 219 for upd := range feed { 220 name := upd.Name 221 ctx := zlog.ContextWithValues(ctx, "updater", name) 222 // Zips have to be written serially, so we spool the fetcher 223 // output to disk, then seek back to the start so it's ready to 224 // read. 225 // 226 // Make sure to close the file in any error cases. The log 227 // prints here use "info" for the application errors and "warn" 228 // for the OS errors that really shouldn't be happening but we 229 // can't do much to recover from. 230 spool, err := os.CreateTemp(tmpDir, tmpPattern) 231 if err != nil { 232 zlog.Warn(ctx).Err(err).Msg("unable to create spool file") 233 continue 234 } 235 spoolname := spool.Name() 236 fp, err := u.fetchOne(ctx, upd, pfps[name], spool) 237 if err != nil { 238 if err := os.Remove(spoolname); err != nil { 239 zlog.Warn(ctx).Str("filename", spoolname).Err(err).Msg("unable to remove spool file") 240 } 241 if err := spool.Close(); err != nil { 242 zlog.Warn(ctx).Str("filename", spoolname).Err(err).Msg("error closing spool file") 243 } 244 zlog.Info(ctx).Err(err).Msg("updater error") 245 continue 246 } 247 if _, err := spool.Seek(0, io.SeekStart); err != nil { 248 if err := os.Remove(spoolname); err != nil { 249 zlog.Warn(ctx).Str("filename", spoolname).Err(err).Msg("unable to remove spool file") 250 } 251 if err := spool.Close(); err != nil { 252 zlog.Warn(ctx).Str("filename", spoolname).Err(err).Msg("error closing spool file") 253 } 254 zlog.Warn(ctx).Str("filename", spoolname).Err(err).Msg("unable to seek to start") 255 continue 256 } 257 res <- &result{ 258 fp: fp, 259 spool: spool, 260 name: name, 261 } 262 } 263 return nil 264 }) 265 } 266 267 return eg.Wait() 268 } 269 270 // AddUpdater writes the results to the zip "z", recording it as time "now". 271 // 272 // This function assumes the result isn't in an error state. 273 func addUpdater(ctx context.Context, z *zip.Writer, now time.Time, r *result) error { 274 defer func() { 275 fn := r.spool.Name() 276 if err := os.Remove(fn); err != nil { 277 zlog.Warn(ctx).Err(err).Str("file", fn).Msg("unable to remove fetch spool") 278 } 279 if err := r.spool.Close(); err != nil { 280 zlog.Warn(ctx).Err(err).Str("file", fn).Msg("unable to close fetch spool") 281 } 282 }() 283 n := r.name 284 // Create a dir entry just to preserve filesystem semantics. Makes 285 // things easier to use upon import. 286 if _, err := z.Create(n + "/"); err != nil { 287 return err 288 } 289 // Write Fingerprint 290 w, err := z.CreateHeader(&zip.FileHeader{ 291 Name: path.Join(n, `fingerprint`), 292 Modified: now, 293 Method: zip.Deflate, 294 }) 295 if err != nil { 296 return err 297 } 298 if _, err := w.Write([]byte(r.fp)); err != nil { 299 return err 300 } 301 // Write a ref 302 w, err = z.CreateHeader(&zip.FileHeader{ 303 Name: path.Join(n, `ref`), 304 Modified: now, 305 Method: zip.Store, 306 }) 307 if err != nil { 308 return err 309 } 310 ref := uuid.New() 311 b, err := ref.MarshalText() 312 if err != nil { 313 return err 314 } 315 if _, err := w.Write(b); err != nil { 316 return err 317 } 318 // Write data 319 w, err = z.CreateHeader(&zip.FileHeader{ 320 Name: path.Join(n, `data`), 321 Modified: now, 322 Method: zstdCompression, 323 }) 324 if err != nil { 325 return err 326 } 327 if _, err := io.Copy(w, r.spool); err != nil { 328 return err 329 } 330 zlog.Debug(ctx). 331 Stringer("ref", ref). 332 Str("name", n). 333 Msg("wrote out fetch results") 334 return nil 335 } 336 337 type result struct { 338 spool *os.File 339 fp driver.Fingerprint 340 name string 341 }