func()

in internal/worker/update.go [62:143]


func (u *updater) update(ctx context.Context) (ur *store.CommitUpdateRecord, err error) {
	// We want the action of reading the old DB record, updating it and
	// writing it back to be atomic. It would be too expensive to do that one
	// record at a time. Ideally we'd process the whole repo commit in one
	// transaction, but Firestore has a limit on how many writes one
	// transaction can do, so the CVE files in the repo are processed in
	// batches, one transaction per batch.
	defer derrors.Wrap(&err, "updater.update(%s)", u.commit.Hash)
	ctx = event.Start(ctx, "updater.update")
	defer event.End(ctx)

	defer func() {
		if err != nil {
			log.Errorf(ctx, "update failed: %v", err)
		} else {
			var nAdded, nModified int64
			if ur != nil {
				nAdded = int64(ur.NumAdded)
				nModified = int64(ur.NumModified)
			}
			log.Infof(ctx, "update succeeded on %s: added %d, modified %d",
				u.commit.Hash, nAdded, nModified)
		}
	}()

	log.Infof(ctx, "update starting on %s", u.commit.Hash)

	// Get all the CVE files.
	// It is cheaper to read all the files from the repo and compare
	// them to the DB in bulk, than to walk the repo and process
	// each file individually.
	files, err := cvelistrepo.Files(u.repo, u.commit)
	if err != nil {
		return nil, err
	}
	// Process files in the same directory together, so we can easily skip
	// the entire directory if it hasn't changed.
	filesByDir, err := groupFilesByDirectory(files)
	if err != nil {
		return nil, err
	}

	// Create a new CommitUpdateRecord to describe this run of doUpdate.
	ur = &store.CommitUpdateRecord{
		StartedAt:  time.Now(),
		CommitHash: u.commit.Hash.String(),
		CommitTime: u.commit.Committer.When,
		NumTotal:   len(files),
	}
	if err := u.st.CreateCommitUpdateRecord(ctx, ur); err != nil {
		return ur, err
	}

	var skippedDirs []string
	const logSkippedEvery = 20 // Log a message every this many skipped directories.
	for _, dirFiles := range filesByDir {
		stats, err := u.updateDirectory(ctx, dirFiles)
		// Change the CommitUpdateRecord in the Store to reflect the results of the directory update.
		if err != nil {
			ur.Error = err.Error()
			if err2 := u.st.SetCommitUpdateRecord(ctx, ur); err2 != nil {
				return ur, fmt.Errorf("update failed with %w, could not set update record: %v", err, err2)
			}
		}
		if stats.skipped {
			skippedDirs = append(skippedDirs, dirFiles[0].DirPath)
			if len(skippedDirs) >= logSkippedEvery {
				log.Infof(ctx, "skipping directory %s and %d others because the hashes match",
					skippedDirs[0], len(skippedDirs)-1)
				skippedDirs = nil
			}
		}
		ur.NumProcessed += stats.numProcessed
		ur.NumAdded += stats.numAdded
		ur.NumModified += stats.numModified
		if err := u.st.SetCommitUpdateRecord(ctx, ur); err != nil {
			return ur, err
		}
	}
	ur.EndedAt = time.Now()
	return ur, u.st.SetCommitUpdateRecord(ctx, ur)
}